diff --git a/src/TemplateUI.Gallery.Android/Effects/CornerRadiusOutlineProvider.cs b/src/TemplateUI.Gallery.Android/Effects/CornerRadiusOutlineProvider.cs new file mode 100644 index 0000000..bff3b8e --- /dev/null +++ b/src/TemplateUI.Gallery.Android/Effects/CornerRadiusOutlineProvider.cs @@ -0,0 +1,25 @@ +using System; +using Android.Graphics; +using Android.Views; +using Xamarin.Forms; + +class CornerRadiusOutlineProvider : ViewOutlineProvider +{ + Element element; + + public CornerRadiusOutlineProvider(Element formsElement) + { + element = formsElement; + } + + public override void GetOutline(Android.Views.View view, Outline outline) + { + float scale = view.Resources.DisplayMetrics.Density; + double width = (double)element.GetValue(VisualElement.WidthProperty) * scale; + double height = (double)element.GetValue(VisualElement.HeightProperty) * scale; + float minDimension = (float)Math.Min(height, width); + float radius = minDimension / 2f; + Rect rect = new Rect(0, 0, (int)width, (int)height); + outline.SetRoundRect(rect, radius); + } +} \ No newline at end of file diff --git a/src/TemplateUI.Gallery.Android/Effects/RoundEffect.cs b/src/TemplateUI.Gallery.Android/Effects/RoundEffect.cs new file mode 100644 index 0000000..4c3c4c6 --- /dev/null +++ b/src/TemplateUI.Gallery.Android/Effects/RoundEffect.cs @@ -0,0 +1,39 @@ +using System; +using Android.Views; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ResolutionGroupName("Xamarin")] +[assembly: ExportEffect(typeof(TemplateUI.Gallery.Droid.Effects.RoundEffect), nameof(TemplateUI.Gallery.Droid.Effects.RoundEffect))] +namespace TemplateUI.Gallery.Droid.Effects +{ + public class RoundEffect : PlatformEffect + { + ViewOutlineProvider originalProvider; + Android.Views.View effectTarget; + + protected override void OnAttached() + { + try + { + effectTarget = Control ?? Container; + originalProvider = effectTarget.OutlineProvider; + effectTarget.OutlineProvider = new CornerRadiusOutlineProvider(Element); + effectTarget.ClipToOutline = true; + } + catch (Exception ex) + { + Console.WriteLine($"Failed to set corner radius: {ex.Message}"); + } + } + + protected override void OnDetached() + { + if (effectTarget != null) + { + effectTarget.OutlineProvider = originalProvider; + effectTarget.ClipToOutline = false; + } + } + } +} \ No newline at end of file diff --git a/src/TemplateUI.Gallery.Android/Renderers/GradientLayoutRenderer.cs b/src/TemplateUI.Gallery.Android/Renderers/GradientLayoutRenderer.cs new file mode 100644 index 0000000..b9ed524 --- /dev/null +++ b/src/TemplateUI.Gallery.Android/Renderers/GradientLayoutRenderer.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using Android.Content; +using Android.Graphics; +using TemplateUI.Controls; +using TemplateUI.Gallery.Droid.Renderers; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(GradientLayout), typeof(GradientLayoutRenderer))] + +// @author: https://stackoverflow.com/users/9654227/na2axl +namespace TemplateUI.Gallery.Droid.Renderers +{ + public class GradientLayoutRenderer : VisualElementRenderer + { + private GradientLayout gradientLayout; + List droidColorSpectrumArgb = new List(); + + public GradientLayoutRenderer(Context context) : base(context) + { + } + + protected override void DispatchDraw(global::Android.Graphics.Canvas canvas) + { + int startX; + int startY; + int endX; + int endY; + + switch (this.gradientLayout.Mode) + { + case GradientColorStackMode.ToLeft: + startX = 0; + startY = 0; + endX = Width; + endY = 0; + break; + case GradientColorStackMode.ToRight: + startX = Width; + startY = 0; + endX = 0; + endY = 0; + break; + case GradientColorStackMode.ToBottom: + startX = 0; + startY = Height; + endX = 0; + endY = 0; + break; + case GradientColorStackMode.ToTop: + startX = 0; + startY = 0; + endX = 0; + endY = Height; + break; + case GradientColorStackMode.ToTopLeft: + startX = 0; + startY = 0; + endX = Width; + endY = Height; + break; + case GradientColorStackMode.ToTopRight: + startX = Width; + startY = 0; + endX = 0; + endY = Height; + break; + case GradientColorStackMode.ToBottomLeft: + startX = 0; + startY = Height; + endX = Width; + endY = 0; + break; + case GradientColorStackMode.ToBottomRight: + startX = Width; + startY = Height; + endX = 0; + endY = 0; + break; + default: + startX = 0; + startY = 0; + endX = 0; + endY = Height; + break; + } + + LinearGradient gradient = new LinearGradient(startX, startY, endX, endY, colors: this.droidColorSpectrumArgb.ToArray(), positions: null, tile: Shader.TileMode.Clamp); + + var paint = new Android.Graphics.Paint() + { + Dither = true, + }; + paint.SetShader(gradient); + canvas.DrawRect(0, 0, Width, Height, paint); + base.DispatchDraw(canvas); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.OldElement != null || Element == null) + { + return; + } + try + { + this.gradientLayout = e.NewElement as GradientLayout; + List colorSpectrum = new List(); + colorSpectrum.AddRange(gradientLayout.Colors); + + List droidColorSpectrum = new List(); + foreach (Xamarin.Forms.Color col in colorSpectrum) + { + droidColorSpectrum.Add(col.ToAndroid()); + } + + foreach (Android.Graphics.Color droidColor in droidColorSpectrum) + { + this.droidColorSpectrumArgb.Add(droidColor.ToArgb()); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(@"ERROR:", ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/TemplateUI.Gallery.Android/Renderers/OpacityGradientLayoutRenderer.cs b/src/TemplateUI.Gallery.Android/Renderers/OpacityGradientLayoutRenderer.cs new file mode 100644 index 0000000..c3ced3d --- /dev/null +++ b/src/TemplateUI.Gallery.Android/Renderers/OpacityGradientLayoutRenderer.cs @@ -0,0 +1,82 @@ +using System; +using System.ComponentModel; +using Android.Content; +using Android.Graphics; +using TemplateUI.Controls; +using TemplateUI.Gallery.Droid.Renderers; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(OpacityGradientLayout), typeof(OpacityGradientLayoutRenderer))] + +// @author: https://stackoverflow.com/users/9654227/na2axl +namespace TemplateUI.Gallery.Droid.Renderers +{ + public class OpacityGradientLayoutRenderer : VisualElementRenderer + { + private OpacityGradientLayout opacityGradientLayout; + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + Invalidate(); + } + + public OpacityGradientLayoutRenderer(Context context) : base(context) + { + // REDRAW + this.SetWillNotDraw(false); + } + + protected override void DispatchDraw(global::Android.Graphics.Canvas canvas) + { + // horizontal gradient + Xamarin.Forms.Color selectedColor = Xamarin.Forms.Color.Red; + if (opacityGradientLayout != null) + { + selectedColor = this.opacityGradientLayout.SelectedColor; + } + var horizontalGradient = new LinearGradient(0, 0, Width, 0, Android.Graphics.Color.White, selectedColor.ToAndroid(), Shader.TileMode.Clamp); + + // vertical gradient + Xamarin.Forms.Color alpha100 = Xamarin.Forms.Color.FromHsla(Xamarin.Forms.Color.Black.Hue, 1.0d, 0.5d, 0.0d); + var verticalGradient = new LinearGradient(0, 0, 0, Height, alpha100.ToAndroid(), Android.Graphics.Color.Black, Shader.TileMode.Clamp); + + // draw horizontal gradient + var horizontalPaint = new Android.Graphics.Paint() + { + Dither = true, + }; + horizontalPaint.SetShader(horizontalGradient); + canvas.DrawRect(0, 0, Width, Height, horizontalPaint); + + // draw vertical gradient + var verticalPaint = new Android.Graphics.Paint() + { + Dither = true, + }; + verticalPaint.SetShader(verticalGradient); + canvas.DrawRect(0, 0, Width, Height, verticalPaint); + + base.DispatchDraw(canvas); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.OldElement != null || Element == null) + { + return; + } + try + { + this.opacityGradientLayout = e.NewElement as OpacityGradientLayout; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(@"ERROR:", ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/TemplateUI.Gallery.Android/Renderers/RadialPickerRenderer.cs b/src/TemplateUI.Gallery.Android/Renderers/RadialPickerRenderer.cs new file mode 100644 index 0000000..4ebc89e --- /dev/null +++ b/src/TemplateUI.Gallery.Android/Renderers/RadialPickerRenderer.cs @@ -0,0 +1,86 @@ +using System; +using System.ComponentModel; +using Android.Content; +using Android.Graphics; +using TemplateUI.Controls; +using TemplateUI.Gallery.Droid.Renderers; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(RadialPicker), typeof(RadialPickerRenderer))] + +namespace TemplateUI.Gallery.Droid.Renderers +{ + public class RadialPickerRenderer : VisualElementRenderer + { + private OpacityGradientLayout opacityGradientLayout; + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + Invalidate(); + } + + public RadialPickerRenderer(Context context) : base(context) + { + // REDRAW + this.SetWillNotDraw(false); + } + + protected override void DispatchDraw(global::Android.Graphics.Canvas canvas) + { + // radial gradient + Xamarin.Forms.Color selectedColor = Xamarin.Forms.Color.Red; + if (opacityGradientLayout != null) + { + selectedColor = this.opacityGradientLayout.SelectedColor; + } + + var fromSaturation = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 1.0d); + var toSaturation = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.0d); + var radialGradient = new RadialGradient(300.0f, 300.0f, 300.0f, fromSaturation.ToAndroid(), toSaturation.ToAndroid(), Shader.TileMode.Clamp); + + // sweep gradient + int[] colors = new int[] { Android.Graphics.Color.Red.ToArgb(), Android.Graphics.Color.Yellow.ToArgb(), Android.Graphics.Color.Green.ToArgb(), Android.Graphics.Color.Cyan.ToArgb(), Android.Graphics.Color.Blue.ToArgb(), Android.Graphics.Color.Magenta.ToArgb(), Android.Graphics.Color.Red.ToArgb() }; + float[] positions = new float[0]; + var sweepGradient = new SweepGradient(300.0f, 300.0f, colors, positions: null); + + // draw vertical gradient + var verticalPaint = new Android.Graphics.Paint() + { + Dither = true, + }; + verticalPaint.SetShader(sweepGradient); + canvas.DrawRect(0, 0, Width, Height, verticalPaint); + + // draw horizontal gradient + var horizontalPaint = new Android.Graphics.Paint() + { + Dither = true, + }; + horizontalPaint.SetShader(radialGradient); + canvas.DrawRect(0, 0, Width, Height, horizontalPaint); + + + base.DispatchDraw(canvas); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.OldElement != null || Element == null) + { + return; + } + try + { + this.opacityGradientLayout = e.NewElement as OpacityGradientLayout; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(@"ERROR:", ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/TemplateUI.Gallery.Android/TemplateUI.Gallery.Android.csproj b/src/TemplateUI.Gallery.Android/TemplateUI.Gallery.Android.csproj index 2aa824f..66809f5 100644 --- a/src/TemplateUI.Gallery.Android/TemplateUI.Gallery.Android.csproj +++ b/src/TemplateUI.Gallery.Android/TemplateUI.Gallery.Android.csproj @@ -41,7 +41,6 @@ prompt 4 true - false @@ -51,6 +50,9 @@ + + ..\..\..\..\..\..\Library\Frameworks\Xamarin.iOS.framework\Versions\14.0.0.0\lib\mono\Xamarin.iOS\Xamarin.iOS.dll + @@ -60,6 +62,11 @@ + + + + + @@ -254,12 +261,20 @@ - + + + + + {D9A11260-71D9-42DB-969F-733793E98E34} TemplateUI.Gallery + + {E3954AAF-D1E4-4BDD-A25B-C3A22D436501} + TemplateUI + diff --git a/src/TemplateUI.Gallery.iOS/Effects/RoundEffect.cs b/src/TemplateUI.Gallery.iOS/Effects/RoundEffect.cs new file mode 100644 index 0000000..50e5410 --- /dev/null +++ b/src/TemplateUI.Gallery.iOS/Effects/RoundEffect.cs @@ -0,0 +1,51 @@ +using System; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ResolutionGroupName("Xamarin")] +[assembly: ExportEffect(typeof(TemplateUI.Gallery.iOS.Effects.RoundEffect), nameof(TemplateUI.Gallery.iOS.Effects.RoundEffect))] +namespace TemplateUI.Gallery.iOS.Effects +{ + public class RoundEffect : PlatformEffect + { + nfloat originalRadius; + UIKit.UIView effectTarget; + + protected override void OnAttached() + { + try + { + effectTarget = Control ?? Container; + originalRadius = effectTarget.Layer.CornerRadius; + effectTarget.ClipsToBounds = true; + effectTarget.Layer.CornerRadius = CalculateRadius(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to set corner radius: {ex.Message}"); + } + } + + protected override void OnDetached() + { + if (effectTarget != null) + { + effectTarget.ClipsToBounds = false; + if (effectTarget.Layer != null) + { + effectTarget.Layer.CornerRadius = originalRadius; + } + } + } + + float CalculateRadius() + { + double width = (double)Element.GetValue(VisualElement.WidthRequestProperty); + double height = (double)Element.GetValue(VisualElement.HeightRequestProperty); + float minDimension = (float)Math.Min(height, width); + float radius = minDimension / 2f; + + return radius; + } + } +} \ No newline at end of file diff --git a/src/TemplateUI.Gallery.iOS/Renderers/GradientLayoutRenderer.cs b/src/TemplateUI.Gallery.iOS/Renderers/GradientLayoutRenderer.cs new file mode 100644 index 0000000..c9a53f0 --- /dev/null +++ b/src/TemplateUI.Gallery.iOS/Renderers/GradientLayoutRenderer.cs @@ -0,0 +1,88 @@ +using System.ComponentModel; +using CoreAnimation; +using CoreGraphics; +using TemplateUI.Controls; +using TemplateUI.Gallery.iOS.Renderers; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(GradientLayout), typeof(GradientLayoutRenderer))] + +namespace TemplateUI.Gallery.iOS.Renderers +{ + public class GradientLayoutRenderer : VisualElementRenderer + { + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + SetNeedsDisplay(); + } + + public override void Draw(CGRect rect) + { + base.Draw(rect); + + GradientLayout layout = (GradientLayout)Element; + + CGColor[] colors = new CGColor[layout.Colors.Length]; + + for (int i = 0, l = colors.Length; i < l; i++) + { + colors[i] = layout.Colors[i].ToCGColor(); + } + + var gradientLayer = new CAGradientLayer(); + + switch (layout.Mode) + { + default: + case GradientColorStackMode.ToRight: + gradientLayer.StartPoint = new CGPoint(0, 0.5); + gradientLayer.EndPoint = new CGPoint(1, 0.5); + break; + case GradientColorStackMode.ToLeft: + gradientLayer.StartPoint = new CGPoint(1, 0.5); + gradientLayer.EndPoint = new CGPoint(0, 0.5); + break; + case GradientColorStackMode.ToTop: + gradientLayer.StartPoint = new CGPoint(0.5, 0); + gradientLayer.EndPoint = new CGPoint(0.5, 1); + break; + case GradientColorStackMode.ToBottom: + gradientLayer.StartPoint = new CGPoint(0.5, 1); + gradientLayer.EndPoint = new CGPoint(0.5, 0); + break; + case GradientColorStackMode.ToTopLeft: + gradientLayer.StartPoint = new CGPoint(1, 0); + gradientLayer.EndPoint = new CGPoint(0, 1); + break; + case GradientColorStackMode.ToTopRight: + gradientLayer.StartPoint = new CGPoint(0, 1); + gradientLayer.EndPoint = new CGPoint(1, 0); + break; + case GradientColorStackMode.ToBottomLeft: + gradientLayer.StartPoint = new CGPoint(1, 1); + gradientLayer.EndPoint = new CGPoint(0, 0); + break; + case GradientColorStackMode.ToBottomRight: + gradientLayer.StartPoint = new CGPoint(0, 0); + gradientLayer.EndPoint = new CGPoint(1, 1); + break; + } + + gradientLayer.Frame = rect; + gradientLayer.Colors = colors; + + // gradientlayer not existed yet + if (NativeView.Layer.Sublayers.Length == 1) + { + NativeView.Layer.InsertSublayer(gradientLayer, 0); + } + // gradientlayer already existing + else if (NativeView.Layer.Sublayers.Length == 2) + { + NativeView.Layer.ReplaceSublayer(NativeView.Layer.Sublayers[0], gradientLayer); + } + } + } +} diff --git a/src/TemplateUI.Gallery.iOS/Renderers/OpacityGradientLayoutRenderer.cs b/src/TemplateUI.Gallery.iOS/Renderers/OpacityGradientLayoutRenderer.cs new file mode 100644 index 0000000..81c2984 --- /dev/null +++ b/src/TemplateUI.Gallery.iOS/Renderers/OpacityGradientLayoutRenderer.cs @@ -0,0 +1,110 @@ +using System.ComponentModel; +using CoreAnimation; +using CoreGraphics; +using TemplateUI.Controls; +using TemplateUI.Gallery.iOS.Renderers; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(OpacityGradientLayout), typeof(OpacityGradientLayoutRenderer))] + +namespace TemplateUI.Gallery.iOS.Renderers +{ + public class OpacityGradientLayoutRenderer : VisualElementRenderer + { + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + SetNeedsDisplay(); + } + + private CAGradientLayer gradientLayer; + + public override void Draw(CGRect rect) + { + base.Draw(rect); + OpacityGradientLayout layout = (OpacityGradientLayout)Element; + + if (gradientLayer == null) + { + gradientLayer = new CAGradientLayer(); + } + + switch (layout.Mode) + { + default: + case GradientColorStackMode.ToRight: + gradientLayer.StartPoint = new CGPoint(0, 0.5); + gradientLayer.EndPoint = new CGPoint(1, 0.5); + break; + case GradientColorStackMode.ToLeft: + gradientLayer.StartPoint = new CGPoint(1, 0.5); + gradientLayer.EndPoint = new CGPoint(0, 0.5); + break; + case GradientColorStackMode.ToTop: + gradientLayer.StartPoint = new CGPoint(0.5, 0); + gradientLayer.EndPoint = new CGPoint(0.5, 1); + break; + case GradientColorStackMode.ToBottom: + gradientLayer.StartPoint = new CGPoint(0.5, 1); + gradientLayer.EndPoint = new CGPoint(0.5, 0); + break; + case GradientColorStackMode.ToTopLeft: + gradientLayer.StartPoint = new CGPoint(1, 0); + gradientLayer.EndPoint = new CGPoint(0, 1); + break; + case GradientColorStackMode.ToTopRight: + gradientLayer.StartPoint = new CGPoint(0, 1); + gradientLayer.EndPoint = new CGPoint(1, 0); + break; + case GradientColorStackMode.ToBottomLeft: + gradientLayer.StartPoint = new CGPoint(1, 1); + gradientLayer.EndPoint = new CGPoint(0, 0); + break; + case GradientColorStackMode.ToBottomRight: + gradientLayer.StartPoint = new CGPoint(0, 0); + gradientLayer.EndPoint = new CGPoint(1, 1); + break; + } + + gradientLayer.Frame = rect; + gradientLayer.Colors = new CGColor[] { + Color.White.ToCGColor(), + layout.SelectedColor.ToCGColor() + }; + + var maskLayer = new CAGradientLayer(); + var lightZero = Color.FromHsla(0d, 1d, 0, 0.0d).ToCGColor(); + var light100 = Color.FromHsla(0d, 1d, 0, 1.0d).ToCGColor(); + CGColor[] maskedColors = + { + lightZero, + light100 + }; + maskLayer.Colors = maskedColors; + maskLayer.StartPoint = new CGPoint(x: 0.5, y: 0); + maskLayer.EndPoint = new CGPoint(x: 0.5, y: 1); + maskLayer.Frame = rect; + + /** + * LAYER 0: Controls + * LAYER 1: MaskLayer + * LAYER 2: GradientLayer + */ + if (NativeView.Layer.Sublayers.Length == 1) + { + NativeView.Layer.InsertSublayer(gradientLayer, 0); + } + + if (NativeView.Layer.Sublayers.Length == 2) + { + NativeView.Layer.InsertSublayer(maskLayer, 1); + } + + if (NativeView.Layer.Sublayers.Length == 3) + { + NativeView.Layer.ReplaceSublayer(NativeView.Layer.Sublayers[1], maskLayer); + } + } + } +} diff --git a/src/TemplateUI.Gallery.iOS/Renderers/RadialPickerRenderer.cs b/src/TemplateUI.Gallery.iOS/Renderers/RadialPickerRenderer.cs new file mode 100644 index 0000000..5d8dc3e --- /dev/null +++ b/src/TemplateUI.Gallery.iOS/Renderers/RadialPickerRenderer.cs @@ -0,0 +1,66 @@ +using System.ComponentModel; +using CoreAnimation; +using CoreGraphics; +using TemplateUI.Controls; +using TemplateUI.Gallery.iOS.Renderers; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(RadialPicker), typeof(RadialGradientLayerRenderer))] + +namespace TemplateUI.Gallery.iOS.Renderers +{ + public class RadialGradientLayerRenderer : VisualElementRenderer + { + public RadialGradientLayerRenderer() + { + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + SetNeedsDisplay(); + } + + public override void Draw(CGRect rect) + { + base.Draw(rect); + + RadialPicker radialPicker = (RadialPicker)Element; + + // Conic Hue + var gradientLayer = new CAGradientLayer(); + gradientLayer.LayerType = CAGradientLayerType.Conic; + gradientLayer.Colors = new CGColor[] { UIColor.Red.CGColor, UIColor.Yellow.CGColor, UIColor.Green.CGColor, UIColor.Cyan.CGColor, UIColor.Blue.CGColor, UIColor.Magenta.CGColor, UIColor.Red.CGColor }; + gradientLayer.StartPoint = new CGPoint(x: 0.5, y: 0.5); + gradientLayer.EndPoint = new CGPoint(x: 0.5, y: 0); + gradientLayer.Frame = rect; + + // Radial Saturation + var radialSaturation = new CAGradientLayer(); + radialSaturation.LayerType = CAGradientLayerType.Radial; + var fromColor = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 1.0d); + var fromColor1 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.9d); + var fromColor2 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.8d); + var fromColor3 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.7d); + var fromColor4 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.6d); + var fromColor5 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.5d); + var fromColor6 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.4d); + var fromColor7 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.3d); + var fromColor8 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.2d); + var fromColor9 = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.1d); + var toColor = Xamarin.Forms.Color.FromRgba(1.0d, 1.0d, 1.0d, 0.0d); + //radialSaturation.Colors = new CGColor[] { fromColor.ToCGColor(), fromColor1.ToCGColor(), fromColor2.ToCGColor(), fromColor3.ToCGColor(), fromColor4.ToCGColor(), fromColor5.ToCGColor(), fromColor6.ToCGColor(), fromColor7.ToCGColor(), fromColor8.ToCGColor(), fromColor9.ToCGColor(), toColor.ToCGColor() }; + radialSaturation.Colors = new CGColor[] { fromColor.ToCGColor(), toColor.ToCGColor() }; + CGPoint center = new CGPoint(x: 0.5, y: 0.5); + radialSaturation.StartPoint = center; + double radius = 1.0d; + radialSaturation.EndPoint = new CGPoint(x: radius, y: radius); + radialSaturation.Frame = rect; + + NativeView.Layer.InsertSublayer(radialSaturation, 0); + NativeView.Layer.InsertSublayer(gradientLayer, 0); + } + } +} \ No newline at end of file diff --git a/src/TemplateUI.Gallery.iOS/TemplateUI.Gallery.iOS.csproj b/src/TemplateUI.Gallery.iOS/TemplateUI.Gallery.iOS.csproj index 4f882b2..fbbb553 100644 --- a/src/TemplateUI.Gallery.iOS/TemplateUI.Gallery.iOS.csproj +++ b/src/TemplateUI.Gallery.iOS/TemplateUI.Gallery.iOS.csproj @@ -69,6 +69,10 @@ + + + + @@ -132,6 +136,10 @@ {D9A11260-71D9-42DB-969F-733793E98E34} TemplateUI.Gallery + + {E3954AAF-D1E4-4BDD-A25B-C3A22D436501} + TemplateUI + @@ -164,9 +172,11 @@ + - + + \ No newline at end of file diff --git a/src/TemplateUI.Gallery/ViewModels/MainViewModel.cs b/src/TemplateUI.Gallery/ViewModels/MainViewModel.cs index 5c3579e..0447346 100644 --- a/src/TemplateUI.Gallery/ViewModels/MainViewModel.cs +++ b/src/TemplateUI.Gallery/ViewModels/MainViewModel.cs @@ -61,6 +61,7 @@ void LoadData() new GalleryItem { Title = "ChatBubble", SubTitle = "Allow to show a speech bubble message.", Icon = "chatbubble.png", Color = Color.DarkSeaGreen }, new GalleryItem { Title = "CircularLayout", SubTitle = "Is a simple Layout derivative that lays out its children in a circular arrangement.", Icon = "circularlayout.png", Color = Color.BlueViolet }, //new GalleryItem { Title = "CircleProgressBar", SubTitle = "Shows a control that indicates the progress percentage of an on-going operation by circular shape.", Icon = "circleprogressbar.png", Color = Color.LightGray, Status = GalleryItemStatus.InProgress }, + new GalleryItem { Title = "ColorPicker", SubTitle = "Picker for choosing Color.", Icon = "circularlayout.png", Color = Color.Red}, new GalleryItem { Title = "ComparerView", SubTitle = "Provides an option for displaying a split-screen of two views, which can help you to make comparisons.", Icon = "comparerview.png", Color = Color.DarkViolet, Status = GalleryItemStatus.InProgress }, new GalleryItem { Title = "DataVisualization", SubTitle = "Several series graphs.", Icon = "chart.png", Color = Color.LightCoral, Status = GalleryItemStatus.Preview }, new GalleryItem { Title = "Divider", SubTitle = "Displays a separator between views.", Icon = "divider.png", Color = Color.Orchid }, diff --git a/src/TemplateUI.Gallery/Views/ColorPickerGallery.xaml b/src/TemplateUI.Gallery/Views/ColorPickerGallery.xaml new file mode 100644 index 0000000..884e5de --- /dev/null +++ b/src/TemplateUI.Gallery/Views/ColorPickerGallery.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/TemplateUI.Gallery/Views/MainView.xaml.cs b/src/TemplateUI.Gallery/Views/MainView.xaml.cs index d64e4b3..6e99ab4 100644 --- a/src/TemplateUI.Gallery/Views/MainView.xaml.cs +++ b/src/TemplateUI.Gallery/Views/MainView.xaml.cs @@ -25,6 +25,11 @@ void OnCarouselViewClicked(object sender, EventArgs e) Navigation.PushAsync(new CarouselViewGallery()); } + void OnColorPickerClicked(object sender, EventArgs e) + { + Navigation.PushAsync(new ColorPickerGallery()); + } + void OnDataVisualizationClicked(object sender, EventArgs e) { Navigation.PushAsync(new DataVisualizationGallery()); diff --git a/src/TemplateUI/Controls/ColorPicker/ColorPicker.cs b/src/TemplateUI/Controls/ColorPicker/ColorPicker.cs new file mode 100644 index 0000000..4f9bd9e --- /dev/null +++ b/src/TemplateUI/Controls/ColorPicker/ColorPicker.cs @@ -0,0 +1,1039 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Runtime.CompilerServices; +using TemplateUI.Helpers; +using Xamarin.Forms; + +namespace TemplateUI.Controls +{ + public class ColorPicker : TemplatedView, INotifyPropertyChanged + { + private enum EntryName + { + RED, + GREEN, + BLUE, + HUE, + SATURATION, + LUMINOSITY + } + + const string ElementRectSatLumPicker = "PART_Rect_SatLumPicker"; + const string ElementRectSatLumPickerThumb = "PART_Rect_SatLumPickerThumb"; + const string ElementRectHuePicker = "PART_Rect_HuePicker"; + const string ElementRectHuePickerThumb = "PART_Rect_HuePickerThumb"; + const string ElementRadialSatHuePicker = "PART_Radial_SatHuePicker"; + const string ElementRadialSatHuePickerThumb = "PART_Radial_SatHuePickerThumb"; + const string ElementRadialLumPicker = "PART_Radial_LumPicker"; + const string ElementRadialLumPickerThumb = "PART_Radial_LumPickerThumb"; + const string ElementEntryRed = "PART_ENTRY_red"; + const string ElementEntryGreen = "PART_ENTRY_green"; + const string ElementEntryBlue = "PART_ENTRY_blue"; + const string ElementEntryHue = "PART_ENTRY_hue"; + const string ElementEntrySaturation = "PART_ENTRY_saturation"; + const string ElementEntryLuminosity = "PART_ENTRY_luminosity"; + + Frame _rectSatLumPickerThumb; + Frame _rectHuePickerThumb; + Frame _radialSatHuePickerThumb; + Frame _radialLumPickerThumb; + OpacityGradientLayout _rectSatLumPicker; + GradientLayout _rectHuePicker; + RadialPicker _radialSatHuePicker; + GradientLayout _radialLumPicker; + Entry _entryRed; + Entry _entryGreen; + Entry _entryBlue; + Entry _entryHue; + Entry _entrySaturation; + Entry _entryLuminosity; + + double _rectSatLumPickerThumbPreviousPositionX; + double _rectSatLumPickerThumbPreviousPostionY; + double _rectHuePickerThumbPreviousPostionY; + double _radialSatHuePickerThumbPreviousPositionX; + double _radialSatHuePickerThumbPreviousPositionY; + double _radialLumPickerThumbPreviousPositionY; + + private Dictionary isUserInput = new Dictionary(); + + /************************************************** + * Bindable Properties + **************************************************/ + /********** + * Shared + **********/ + public static readonly BindableProperty PickedColorProperty = + BindableProperty.Create(nameof(PickedColor), typeof(Color), typeof(ColorPicker), Color.White, + propertyChanged: OnPickedColorChanged); + + static void OnPickedColorChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is ColorPicker colorPicker) + { + colorPicker.UpdateRectHuePickerThumbOnColorChanged(); + colorPicker.UpdateRectSatLumPickerThumbOnColorChanged(); + colorPicker.UpdateRadialSatHuePickerThumbOnColorChanged(); + colorPicker.UpdateRadialLumPickerOnColorChanged(); + colorPicker.UpdateRadialLumPickerThumbOnColorChanged(); + } + } + + public Color PickedColor + { + get => (Color)GetValue(PickedColorProperty); + set { SetValue(PickedColorProperty, value); } + } + /********** + * RECT Saturation Luminosity Picker + **********/ + public static readonly BindableProperty ValueXRectSatLumPickerThumbProperty = + BindableProperty.Create(nameof(ValueXRectSatLumPickerThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRectSatLumValueChanged); + + public static readonly BindableProperty ValueYRectSatLumPickerThumbProperty = + BindableProperty.Create(nameof(ValueYRectSatLumPickerThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRectSatLumValueChanged); + + static void OnRectSatLumValueChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is ColorPicker colorPicker) + { + colorPicker.ValueChanged?.Invoke(bindable, new ValueChangedEventArgs((double)newValue)); + colorPicker.UpdateRectSatLumPickerThumb(); + colorPicker.CalculatePickedColorBasedOnRectThumbs(); + } + } + + public double ValueXRectSatLumPickerThumb + { + get => (double)GetValue(ValueXRectSatLumPickerThumbProperty); + set { SetValue(ValueXRectSatLumPickerThumbProperty, value); } + } + + public double ValueYRectSatLumPickerThumb + { + get => (double)GetValue(ValueYRectSatLumPickerThumbProperty); + set { SetValue(ValueYRectSatLumPickerThumbProperty, value); } + } + + /********** + * RECT Hue Picker + **********/ + public static readonly BindableProperty ValueXRectHuePickerThumbProperty = + BindableProperty.Create(nameof(ValueXRectHuePickerThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRectHueValueChanged); + + public double ValueXRectHuePickerThumb + { + get => (double)GetValue(ValueXRectHuePickerThumbProperty); + set { SetValue(ValueXRectHuePickerThumbProperty, value); } + } + + public static readonly BindableProperty ValueYRectHuePickerThumbProperty = + BindableProperty.Create(nameof(ValueRectHuePickerThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRectHueValueChanged); + + public double ValueRectHuePickerThumb + { + get => (double)GetValue(ValueYRectHuePickerThumbProperty); + set { SetValue(ValueYRectHuePickerThumbProperty, value); } + } + + static void OnRectHueValueChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is ColorPicker colorPicker) + { + colorPicker.ValueChanged?.Invoke(bindable, new ValueChangedEventArgs((double)newValue)); + colorPicker.UpdateRectHuePickerThumb(); + colorPicker.CalculatePickedColorBasedOnRectThumbs(); + } + } + + /********** + * RADIAL Saturation Hue Picker + **********/ + public static readonly BindableProperty ValueXRadialSatHuePickerThumbProperty = + BindableProperty.Create(nameof(ValueXRadialSatHuePickerThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRadialPickerSatHueValueChanged); + + public static readonly BindableProperty ValueYRadialSatHuePickerThumbProperty = + BindableProperty.Create(nameof(ValueYRadialSatHuePickerThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRadialPickerSatHueValueChanged); + + static void OnRadialPickerSatHueValueChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is ColorPicker colorPicker) + { + colorPicker.ValueChanged?.Invoke(bindable, new ValueChangedEventArgs((double)newValue)); + colorPicker.UpdateRadialSatHuePickerThumb(); + colorPicker.CalculatePickedColorBasedOnRadialThumbs(); + } + } + + public double ValueXRadialSatHuePickerThumb + { + get => (double)GetValue(ValueXRadialSatHuePickerThumbProperty); + set { SetValue(ValueXRadialSatHuePickerThumbProperty, value); } + } + + public double ValueYRadialSatHuePickerThumb + { + get => (double)GetValue(ValueYRadialSatHuePickerThumbProperty); + set { SetValue(ValueYRadialSatHuePickerThumbProperty, value); } + } + + /********** + * RADIAL Luminosity Picker + **********/ + public static readonly BindableProperty ValueXRadialLumPickerThumbProperty = + BindableProperty.Create(nameof(ValueXRadialLumPickerThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRadialPickerLumValueChanged); + + public double ValueXRadialLumPickerThumb + { + get => (double)GetValue(ValueXRadialLumPickerThumbProperty); + set { SetValue(ValueXRadialLumPickerThumbProperty, value); } + } + + public static readonly BindableProperty ValueYRadialLumPickerThumbProperty = + BindableProperty.Create(nameof(ValueYRadialLumThumb), typeof(double), typeof(ColorPicker), 0.0d, + propertyChanged: OnRadialPickerLumValueChanged); + + public double ValueYRadialLumThumb + { + get => (double)GetValue(ValueYRadialLumPickerThumbProperty); + set { SetValue(ValueYRadialLumPickerThumbProperty, value); } + } + + static void OnRadialPickerLumValueChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is ColorPicker colorPicker) + { + colorPicker.ValueChanged?.Invoke(bindable, new ValueChangedEventArgs((double)newValue)); + colorPicker.UpdateRadialLumPickerThumb(); + colorPicker.CalculatePickedColorBasedOnRadialThumbs(); + } + } + + /********** + * Helping Properties + **********/ + public static readonly BindableProperty MaximumXProperty = + BindableProperty.Create(nameof(MaximumX), typeof(double), typeof(ColorPicker), 10.0d); + + public double MaximumX + { + get => (double)GetValue(MaximumXProperty); + set { SetValue(MaximumXProperty, value); } + } + + public static readonly BindableProperty MaximumYProperty = + BindableProperty.Create(nameof(MaximumY), typeof(double), typeof(ColorPicker), 10.0d); + + public double MaximumY + { + get => (double)GetValue(MaximumYProperty); + set { SetValue(MaximumYProperty, value); } + } + + /************************************************** + * Properties + **************************************************/ + public string ColorsList + { + get + { + string colorsList = ""; + // 100 different Hues + for (double d = 0.00; d <= 1.0; d = d + 0.01d) + { + Color color = Color.FromHsla(d, 1.0, 0.5); + colorsList = colorsList + color.ToHex() + ","; + } + return colorsList; + } + } + + private string colorsListBrightness = $"{ Color.FromRgba(0, 0, 0, 0).ToHex() },{ Color.FromRgba(0, 0, 0, 255).ToHex() }"; + public string ColorsListBrightness + { + get + { + return this.colorsListBrightness; + } + set + { + this.colorsListBrightness = value; + this.OnPropertyChanged(); + } + } + + // TODO entfernen. wird nicht mehr gebraucht + // This color has a fixed saturation and luminosity + private Color pickedColorForHSL; + public Color PickedColorForHSL + { + get + { + return this.pickedColorForHSL; + } + set + { + this.pickedColorForHSL = value; + OnPropertyChanged(); + } + } + + /************************************************** + * Hooks + **************************************************/ + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _rectSatLumPickerThumb = (Frame)GetTemplateChild(ElementRectSatLumPickerThumb); + _rectSatLumPicker = (OpacityGradientLayout)GetTemplateChild(ElementRectSatLumPicker); + _rectHuePickerThumb = (Frame)GetTemplateChild(ElementRectHuePickerThumb); + _rectHuePicker = (GradientLayout)GetTemplateChild(ElementRectHuePicker); + _radialSatHuePicker = (RadialPicker)GetTemplateChild(ElementRadialSatHuePicker); + _radialSatHuePickerThumb = (Frame)GetTemplateChild(ElementRadialSatHuePickerThumb); + _radialLumPicker = (GradientLayout)GetTemplateChild(ElementRadialLumPicker); + _radialLumPickerThumb = (Frame)GetTemplateChild(ElementRadialLumPickerThumb); + _entryRed = (Entry)GetTemplateChild(ElementEntryRed); + _entryGreen = (Entry)GetTemplateChild(ElementEntryGreen); + _entryBlue = (Entry)GetTemplateChild(ElementEntryBlue); + _entryHue = (Entry)GetTemplateChild(ElementEntryHue); + _entrySaturation = (Entry)GetTemplateChild(ElementEntrySaturation); + _entryLuminosity = (Entry)GetTemplateChild(ElementEntryLuminosity); + + UpdateIsEnabled(); + } + + protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + base.OnPropertyChanged(propertyName); + + if (propertyName == IsEnabledProperty.PropertyName) + UpdateIsEnabled(); + } + + protected override void OnSizeAllocated(double width, double height) + { + base.OnSizeAllocated(width, height); + + UpdateRectSatLumPickerThumb(); + UpdateRectHuePickerThumb(); + UpdateRadialSatHuePickerThumb(); + UpdateRadialLumPickerThumb(); + } + + /************************************************** + * Events + **************************************************/ + public event EventHandler ValueChanged; + + /************************************************** + * Private Methods + **************************************************/ + void UpdateIsEnabled() + { + if (IsEnabled) + { + // registering event handlers + var panGestureRecognizer = new PanGestureRecognizer(); + panGestureRecognizer.PanUpdated += PanGestureRecognizer_PanUpdated; + _rectSatLumPickerThumb.GestureRecognizers.Add(panGestureRecognizer); + + var panGestureRecognizer2 = new PanGestureRecognizer(); + panGestureRecognizer2.PanUpdated += RgbPanGestureRecognizer_PanUpdated; + _rectHuePickerThumb.GestureRecognizers.Add(panGestureRecognizer2); + + var panGestureRecognizer3 = new PanGestureRecognizer(); + panGestureRecognizer3.PanUpdated += RadialPickerThumbPanGestureRecognizer_PanUpdated; + _radialSatHuePickerThumb.GestureRecognizers.Add(panGestureRecognizer3); + + var panGestureRecognizer4 = new PanGestureRecognizer(); + panGestureRecognizer4.PanUpdated += BrightnessPanGestureRecognizer_PanUpdated; + _radialLumPickerThumb.GestureRecognizers.Add(panGestureRecognizer4); + + // R + _entryRed.Completed += _entryRed_Completed; + _entryRed.TextChanged += _entryRed_Changed; + _entryRed.Focused += _entryRed_Focused; + _entryRed.Unfocused += _entryRed_Unfocused; + // G + _entryGreen.Completed += _entryGreen_Completed; + _entryGreen.TextChanged += _entryGreen_Changed; + _entryGreen.Focused += _entryGreen_Focused; + _entryGreen.Unfocused += _entryGreen_Unfocused; + // B + _entryBlue.Completed += _entryBlue_Completed; + _entryBlue.TextChanged += _entryBlue_Changed; + _entryBlue.Focused += _entryBlue_Focused; + _entryBlue.Unfocused += _entryBlue_Unfocused; + // HUE + _entryHue.Completed += _entryHue_Completed; + _entryHue.TextChanged += _entryHue_Changed; + _entryHue.Focused += _entryHue_Focused; + _entryHue.Unfocused += _entryHue_Unfocused; + // SATURATION + _entrySaturation.Completed += _entrySaturation_Completed; + _entrySaturation.TextChanged += _entrySaturation_Changed; + _entrySaturation.Focused += _entrySaturation_Focused; + _entrySaturation.Unfocused += _entrySaturation_Unfocused; + // LUMINOSITY + _entryLuminosity.Completed += _entryLuminosity_Completed; + _entryLuminosity.TextChanged += _entryLuminosity_Changed; + _entryLuminosity.Focused += _entryLuminosity_Focused; + _entryLuminosity.Unfocused += _entryLuminosity_Unfocused; + + // default thumb positions and default values + this._radialSatHuePickerThumb.TranslationX = this._radialSatHuePicker.WidthRequest / 2; + this._radialSatHuePickerThumb.TranslationY = this._radialSatHuePicker.HeightRequest / 2; + } + else + { + // unregistering event handlers + _rectSatLumPickerThumb.GestureRecognizers.Clear(); + _rectHuePickerThumb.GestureRecognizers.Clear(); + _radialSatHuePickerThumb.GestureRecognizers.Clear(); + _radialLumPickerThumb.GestureRecognizers.Clear(); + _entryRed.Completed -= _entryRed_Completed; + _entryRed.TextChanged -= _entryRed_Changed; + _entryGreen.Completed -= _entryGreen_Completed; + _entryGreen.TextChanged -= _entryGreen_Changed; + _entryBlue.Completed -= _entryBlue_Completed; + _entryBlue.TextChanged -= _entryBlue_Changed; + _entryHue.Completed -= _entryHue_Completed; + _entryHue.TextChanged -= _entryHue_Changed; + _entrySaturation.Completed -= _entrySaturation_Completed; + _entrySaturation.TextChanged -= _entrySaturation_Changed; + _entryLuminosity.Completed -= _entryLuminosity_Completed; + _entryLuminosity.TextChanged -= _entrySaturation_Changed; + } + } + + /**************************************** + * Human Interaction Event Handlers + ****************************************/ + // Human Interaction: Luminosity + private void _entryLuminosity_Completed(object sender, EventArgs e) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(_entryLuminosity.Text, out newValue); + if (parseSuccess) + { + _entryLuminosity_Event(newValue); + } + } + + private void _entryLuminosity_Changed(object sender, TextChangedEventArgs e) + { + bool isUserInput = false; + this.isUserInput.TryGetValue(EntryName.LUMINOSITY, out isUserInput); + if (isUserInput) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(e.NewTextValue, out newValue); + if (parseSuccess) + { + _entryLuminosity_Event(newValue); + } + } + } + + private void _entryLuminosity_Event(double newValue) + { + double maxLuminosity = ColorNumberHelper.MaxLuminosityFromSaturation(ColorNumberHelper.FromSourceToTargetSaturation(PickedColor.Saturation)); + double convertedNewValue = ColorNumberHelper.FromTargetToSourceLuminosity(newValue >= maxLuminosity ? maxLuminosity : newValue); + Color newColor = Color.FromHsla(PickedColor.Hue, PickedColor.Saturation, convertedNewValue); + PickedColor = newColor; + PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + // Human Interaction: Saturation + private void _entrySaturation_Completed(object sender, EventArgs e) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(_entrySaturation.Text, out newValue); + if (parseSuccess) + { + _entrySaturation_Event(newValue); + } + } + + private void _entrySaturation_Changed(object sender, TextChangedEventArgs e) + { + bool isUserInput = false; + this.isUserInput.TryGetValue(EntryName.SATURATION, out isUserInput); + if (isUserInput) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(e.NewTextValue, out newValue); + if (parseSuccess) + { + _entrySaturation_Event(newValue); + } + } + } + + private void _entrySaturation_Event(double newValue) + { + double maxSaturation = ColorNumberHelper.MaxSaturationFromLuminosity(ColorNumberHelper.FromSourceToTargetLuminosity(PickedColor.Luminosity)); + double convertedNewValue = ColorNumberHelper.FromTargetToSourceSaturation(newValue >= maxSaturation ? maxSaturation : newValue); + Color newColor = Color.FromHsla(PickedColor.Hue, convertedNewValue, PickedColor.Luminosity); + PickedColor = newColor; + PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + // Human Interaction Hue + private void _entryHue_Completed(object sender, EventArgs e) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(_entryHue.Text, out newValue); + if (parseSuccess) + { + _entryHue_Event(newValue); + } + } + + private void _entryHue_Changed(object sender, TextChangedEventArgs e) + { + bool isUserInput = false; + this.isUserInput.TryGetValue(EntryName.HUE, out isUserInput); + if (isUserInput) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(e.NewTextValue, out newValue); + if (parseSuccess) + { + _entryHue_Event(newValue); + } + } + } + + private void _entryHue_Event(double newValue) + { + double convertedNewValue = ColorNumberHelper.FromTargetToSourceHue(newValue); + Color newColor = Color.FromHsla(convertedNewValue, PickedColor.Saturation, PickedColor.Luminosity); + PickedColor = newColor; + PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + // Human Interaction Blue + private void _entryBlue_Completed(object sender, EventArgs e) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(_entryBlue.Text, out newValue); + if (parseSuccess) + { + _entryBlue_Event(newValue); + } + } + + private void _entryBlue_Changed(object sender, TextChangedEventArgs e) + { + bool isUserInput = false; + this.isUserInput.TryGetValue(EntryName.BLUE, out isUserInput); + if (isUserInput) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(e.NewTextValue, out newValue); + if (parseSuccess) + { + _entryBlue_Event(newValue); + } + } + } + + private void _entryBlue_Event(double newValue) + { + double convertedNewValue = ColorNumberHelper.FromTargetToSourceRGB(newValue); + Color newColor = Color.FromRgb(PickedColor.R, PickedColor.G, convertedNewValue); + PickedColor = newColor; + PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + // Human Interaction Green + private void _entryGreen_Completed(object sender, EventArgs e) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(_entryGreen.Text, out newValue); + if (parseSuccess) + { + _entryGreen_Event(newValue); + } + } + + private void _entryGreen_Changed(object sender, TextChangedEventArgs e) + { + bool isUserInput = false; + this.isUserInput.TryGetValue(EntryName.GREEN, out isUserInput); + if (isUserInput) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(e.NewTextValue, out newValue); + if (parseSuccess) + { + _entryGreen_Event(newValue); + } + } + } + + private void _entryGreen_Event(double newValue) + { + double convertedNewValue = ColorNumberHelper.FromTargetToSourceRGB(newValue); + Color newColor = Color.FromRgb(PickedColor.R, convertedNewValue, PickedColor.B); + PickedColor = newColor; + PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + // Human Interaction Red + private void _entryRed_Completed(object sender, EventArgs e) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(_entryRed.Text, out newValue); + if (parseSuccess) + { + _entryRed_Event(newValue); + } + } + + private void _entryRed_Changed(object sender, TextChangedEventArgs e) + { + bool isUserInput = false; + this.isUserInput.TryGetValue(EntryName.RED, out isUserInput); + if (isUserInput) + { + double newValue = 1.0d; + bool parseSuccess = double.TryParse(e.NewTextValue, out newValue); + if (parseSuccess) + { + _entryRed_Event(newValue); + } + } + } + + private void _entryRed_Event(double newValue) + { + double convertedNewValue = ColorNumberHelper.FromTargetToSourceRGB(newValue); + Color newColor = Color.FromRgb(convertedNewValue, PickedColor.G, PickedColor.B); + PickedColor = newColor; + PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + /** + * Entry Focused & Entry Unfocused are needed for distinguishing between text changed through user and text changed through code, so that there's no + * circular event triggering. + */ + private void _entryLuminosity_Focused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.LUMINOSITY] = true; + } + + private void _entryLuminosity_Unfocused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.LUMINOSITY] = false; + } + + private void _entrySaturation_Focused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.SATURATION] = true; + } + + private void _entrySaturation_Unfocused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.SATURATION] = false; + } + + private void _entryHue_Focused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.HUE] = true; + } + + private void _entryHue_Unfocused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.HUE] = false; + } + + private void _entryRed_Focused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.RED] = true; + } + + private void _entryRed_Unfocused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.RED] = false; + } + + private void _entryGreen_Focused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.GREEN] = true; + } + + private void _entryGreen_Unfocused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.GREEN] = false; + } + + private void _entryBlue_Focused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.BLUE] = true; + } + + private void _entryBlue_Unfocused(object sender, FocusEventArgs e) + { + isUserInput[EntryName.BLUE] = false; + } + + // Human Interaction: Squared Picker Hue + void PanGestureRecognizer_PanUpdated(System.Object sender, Xamarin.Forms.PanUpdatedEventArgs e) + { + + switch (e.StatusType) + { + case GestureStatus.Started: + deactivateUserInput(); + _rectSatLumPickerThumbPreviousPositionX = e.TotalX; + _rectSatLumPickerThumbPreviousPostionY = e.TotalY; + + if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.macOS) + { + _rectSatLumPickerThumbPreviousPositionX += _rectSatLumPickerThumb.TranslationX; + _rectSatLumPickerThumbPreviousPostionY += _rectSatLumPickerThumb.TranslationY; + } + break; + case GestureStatus.Running: + double totalX = _rectSatLumPickerThumbPreviousPositionX + e.TotalX; + double totalY = _rectSatLumPickerThumbPreviousPostionY + e.TotalY; + + if (Device.RuntimePlatform == Device.Android) + { + totalX += _rectSatLumPickerThumb.TranslationX; + totalY += _rectSatLumPickerThumb.TranslationY; + } + + // Keep the position of thumbs in check + var positionX = totalX < 0 ? 0 : totalX > _rectSatLumPicker.Width ? _rectSatLumPicker.Width : totalX; + ValueXRectSatLumPickerThumb = positionX * MaximumX / _rectSatLumPicker.Width; + var positionY = totalY < 0 ? 0 : totalY > _rectSatLumPicker.Height ? _rectSatLumPicker.Height : totalY; + ValueYRectSatLumPickerThumb = positionY * MaximumY / _rectSatLumPicker.Height; + break; + case GestureStatus.Completed: + case GestureStatus.Canceled: + break; + } + } + + // Human Interaction: Squared Picker Luminosity + void RgbPanGestureRecognizer_PanUpdated(System.Object sender, Xamarin.Forms.PanUpdatedEventArgs e) + { + switch (e.StatusType) + { + case GestureStatus.Started: + deactivateUserInput(); + _rectHuePickerThumbPreviousPostionY = e.TotalY; + + if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.macOS) + { + _rectHuePickerThumbPreviousPostionY += _rectHuePickerThumb.TranslationY; + } + break; + case GestureStatus.Running: + double totalY = _rectHuePickerThumbPreviousPostionY + e.TotalY; + + if (Device.RuntimePlatform == Device.Android) + { + totalY += _rectHuePickerThumb.TranslationY; + } + + var positionY = totalY < 0 ? 0 : totalY > _rectHuePicker.Height ? _rectHuePicker.Height : totalY; + ValueRectHuePickerThumb = positionY * MaximumY / _rectHuePicker.Height; + break; + case GestureStatus.Completed: + case GestureStatus.Canceled: + break; + } + } + + // Human Interaction: Radial Picker Hue + void RadialPickerThumbPanGestureRecognizer_PanUpdated(System.Object sender, Xamarin.Forms.PanUpdatedEventArgs e) + { + switch (e.StatusType) + { + case GestureStatus.Started: + deactivateUserInput(); + _radialSatHuePickerThumbPreviousPositionX = e.TotalX; + _radialSatHuePickerThumbPreviousPositionY = e.TotalY; + + if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.macOS) + { + _radialSatHuePickerThumbPreviousPositionX += _radialSatHuePickerThumb.TranslationX; + _radialSatHuePickerThumbPreviousPositionY += _radialSatHuePickerThumb.TranslationY; + } + break; + case GestureStatus.Running: + double totalX = _radialSatHuePickerThumbPreviousPositionX + e.TotalX; + double totalY = _radialSatHuePickerThumbPreviousPositionY + e.TotalY; + + if (Device.RuntimePlatform == Device.Android) + { + totalX += _radialSatHuePickerThumb.TranslationX; + totalY += _radialSatHuePickerThumb.TranslationY; + } + + // Keep the position of thumbs in check + var positionX = totalX < 0 ? 0 : totalX > _radialSatHuePicker.Width ? _radialSatHuePicker.Width : totalX; + ValueXRadialSatHuePickerThumb = positionX * MaximumX / _radialSatHuePicker.Width; + var positionY = totalY < 0 ? 0 : totalY > _radialSatHuePicker.Height ? _radialSatHuePicker.Height : totalY; + ValueYRadialSatHuePickerThumb = positionY * MaximumY / _radialSatHuePicker.Height; + break; + case GestureStatus.Completed: + case GestureStatus.Canceled: + break; + } + } + + // Human Interaction: Radial Picker Luminosity + void BrightnessPanGestureRecognizer_PanUpdated(System.Object sender, Xamarin.Forms.PanUpdatedEventArgs e) + { + switch (e.StatusType) + { + case GestureStatus.Started: + deactivateUserInput(); + _radialLumPickerThumbPreviousPositionY = e.TotalY; + + if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.macOS) + { + _radialLumPickerThumbPreviousPositionY += _radialLumPickerThumb.TranslationY; + } + break; + case GestureStatus.Running: + double totalY = _radialLumPickerThumbPreviousPositionY + e.TotalY; + + if (Device.RuntimePlatform == Device.Android) + { + totalY += _radialLumPickerThumb.TranslationY; + } + + var positionY = totalY < 0 ? 0 : totalY > _radialLumPicker.Height ? _radialLumPicker.Height : totalY; + ValueYRadialLumThumb = positionY * MaximumY / _radialLumPicker.Height; + break; + case GestureStatus.Completed: + case GestureStatus.Canceled: + break; + } + } + + /**************************************** + * Move Thumbs on Human Interaction or Selected Color changed + ****************************************/ + // Move Light & Saturation Thumb on Human Interaction + void UpdateRectSatLumPickerThumb() + { + var positionX = ValueXRectSatLumPickerThumb / MaximumX * _rectSatLumPicker.Width; + var positionY = ValueYRectSatLumPickerThumb / MaximumY * _rectSatLumPicker.Height; + + if (positionX < 0) + positionX = 0; + if (positionY < 0) + positionY = 0; + + _rectSatLumPickerThumb.TranslationX = positionX; + _rectSatLumPickerThumb.TranslationY = positionY; + } + + // Move Hue Thumb on Human Interaction + void UpdateRectHuePickerThumb() + { + var positionX = ValueXRectHuePickerThumb / MaximumX * _rectHuePicker.Width; + var positionY = ValueRectHuePickerThumb / MaximumY * _rectHuePicker.Height; + + if (positionX < 0) + positionX = 0; + if (positionY < 0) + positionY = 0; + + _rectHuePickerThumb.TranslationX = positionX; + _rectHuePickerThumb.TranslationY = positionY; + } + + // Move Radial Picker Hue Thumb on Human Interaction + void UpdateRadialSatHuePickerThumb() + { + var centerPointX = this._radialSatHuePicker.Width / 2; + var centerPointY = this._radialSatHuePicker.Height / 2; + var radius = this._radialSatHuePicker.Width / 2; + + var positionX = ValueXRadialSatHuePickerThumb / MaximumX * _radialSatHuePicker.Width; + var positionY = ValueYRadialSatHuePickerThumb / MaximumY * _radialSatHuePicker.Height; + + var positionXRelativeToCenter = Math.Abs(centerPointX - positionX); + var positionYRelativeToCenter = Math.Abs(centerPointY - positionY); + var hypothenuse = Math.Sqrt(Math.Pow(positionXRelativeToCenter, 2.0) + Math.Pow(positionYRelativeToCenter, 2.0)); + + // outside of circle + if (hypothenuse > radius) + { + return; + } + + if (positionX < 0) + positionX = 0; + if (positionY < 0) + positionY = 0; + + _radialSatHuePickerThumb.TranslationX = positionX; + _radialSatHuePickerThumb.TranslationY = positionY; + } + + // Move Radial Picker Luminosity Thumb on Human Interaction + void UpdateRadialLumPickerThumb() + { + var positionX = ValueXRadialLumPickerThumb / MaximumX * _radialLumPicker.Width; + var positionY = ValueYRadialLumThumb / MaximumY * _radialLumPicker.Height; + + if (positionX < 0) + positionX = 0; + if (positionY < 0) + positionY = 0; + + _radialLumPickerThumb.TranslationX = positionX; + _radialLumPickerThumb.TranslationY = positionY; + } + + // Move Light & Saturation Thumb on SelectedColor changing + void UpdateRectSatLumPickerThumbOnColorChanged() + { + _rectSatLumPickerThumb.TranslationX = PickedColor.Saturation * _rectSatLumPicker.Width; + // the higher the saturation the lower the max luminosity + double maxLuminosity = 100.0d - 0.5d * ColorNumberHelper.FromSourceToTargetSaturation(PickedColor.Saturation); + maxLuminosity = ColorNumberHelper.FromTargetToSourceLuminosity(maxLuminosity); + // luminosity not bigger than max luminosity + double luminosity = PickedColor.Luminosity > maxLuminosity ? maxLuminosity : PickedColor.Luminosity; + _rectSatLumPickerThumb.TranslationY = _rectSatLumPicker.Height - luminosity / maxLuminosity * _rectSatLumPicker.Height; + } + + // Move Hue Thumb on SelectedColor changing + void UpdateRectHuePickerThumbOnColorChanged() + { + _rectHuePickerThumb.TranslationY = PickedColor.Hue * _rectHuePicker.Height; + } + + // Move Radial Picker Hue Thumb on SelectedColor changing + void UpdateRadialSatHuePickerThumbOnColorChanged() + { + var centerPointX = this._radialSatHuePicker.Width / 2; + var centerPointY = this._radialSatHuePicker.Height / 2; + double radius = this._radialSatHuePicker.Width / 2; + double hypothenuse = radius * PickedColor.Saturation; + _radialSatHuePickerThumb.TranslationX = centerPointX + hypothenuse * Math.Sin(ColorNumberHelper.ConvertDegreesToRadians(PickedColor.Hue * 360.0)); + _radialSatHuePickerThumb.TranslationY = centerPointY - hypothenuse * Math.Cos(ColorNumberHelper.ConvertDegreesToRadians(PickedColor.Hue * 360.0)); + } + + // Update Radial Picker Luminosity Appearance + void UpdateRadialLumPickerOnColorChanged() + { + // the higher the saturation the lower the max luminosity + double maxLuminosity = 100.0d - 0.5d * ColorNumberHelper.FromSourceToTargetSaturation(PickedColor.Saturation); + maxLuminosity = ColorNumberHelper.FromTargetToSourceLuminosity(maxLuminosity); + Color pickedColorLumMin = Color.FromHsla(PickedColor.Hue, PickedColor.Saturation, 0.0); + Color pickedColorLumMax = Color.FromHsla(PickedColor.Hue, PickedColor.Saturation, maxLuminosity); + this.ColorsListBrightness = $"{ pickedColorLumMax.ToHex() },{ pickedColorLumMin.ToHex() }"; + } + + // Move Radial Picker Luminosity Thumb on SelectedColor changing + void UpdateRadialLumPickerThumbOnColorChanged() + { + // the higher the saturation the lower the max luminosity + double maxLuminosity = 100.0d - 0.5d * ColorNumberHelper.FromSourceToTargetSaturation(PickedColor.Saturation); + maxLuminosity = ColorNumberHelper.FromTargetToSourceLuminosity(maxLuminosity); + // luminosity not bigger than max luminosity + double luminosity = PickedColor.Luminosity > maxLuminosity ? maxLuminosity : PickedColor.Luminosity; + _radialLumPickerThumb.TranslationY = _radialLumPicker.Height - luminosity / maxLuminosity * _radialLumPicker.Height; + } + + private void deactivateUserInput() + { + /** + * isUserInput komplett auf false setzen + */ + //foreach(KeyValuePair item in isUserInput) + //{ + // isUserInput[item.Key] = false; + //} + this.isUserInput[EntryName.RED] = false; + this.isUserInput[EntryName.GREEN] = false; + this.isUserInput[EntryName.BLUE] = false; + this.isUserInput[EntryName.HUE] = false; + this.isUserInput[EntryName.SATURATION] = false; + this.isUserInput[EntryName.LUMINOSITY] = false; + } + + private void CalculatePickedColorBasedOnRectThumbs() + { + // HUE (0.0d to 360.0d) + double hue = 360.0d * (_rectHuePickerThumb.TranslationY / _rectHuePicker.Height); + + // LUMINOSITY (0.0d to 100.0d) + double positionXhslInv = _rectSatLumPicker.Width - _rectSatLumPickerThumb.TranslationX; + double positionYhslInv = _rectSatLumPicker.Height - _rectSatLumPickerThumb.TranslationY; + double minimumLuminosity = 50.0d; + double maximumLuminosity = minimumLuminosity + (positionXhslInv / _rectSatLumPicker.Width * 50.0d); + double luminosity = positionYhslInv / _rectSatLumPicker.Height * maximumLuminosity; + + // SATURATION (0.0d to 100.0d) + double saturation = _rectSatLumPickerThumb.TranslationX / _rectSatLumPicker.Width * 100; + + // SETTING PICKED COLOR + double technicalHue = ColorNumberHelper.FromTargetToSourceHue(hue); + double technicalSaturation = ColorNumberHelper.FromTargetToSourceSaturation(saturation); + double technicalLuminosity = ColorNumberHelper.FromTargetToSourceLuminosity(luminosity); + this.PickedColor = Color.FromHsla(technicalHue, technicalSaturation, technicalLuminosity); + this.PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + private void CalculatePickedColorBasedOnRadialThumbs() + { + // HUE (0.0d to 360.0d) + var positionX = _radialSatHuePickerThumb.TranslationX; + var positionY = _radialSatHuePickerThumb.TranslationY; + var centerPointX = this._radialSatHuePicker.Width / 2; + var centerPointY = this._radialSatHuePicker.Height / 2; + var positionXRelativeToCenter = positionX - centerPointX; + var positionYRelativeToCenter = centerPointY - positionY; + double hue = calculateAngleClockwise(positionXRelativeToCenter, positionYRelativeToCenter); + + // SATURATION (0.0d to 100.0d) + var radius = this._radialSatHuePicker.Width / 2; + var hypothenuse = Math.Sqrt(Math.Pow(positionXRelativeToCenter, 2.0) + Math.Pow(positionYRelativeToCenter, 2.0)); + double saturation = hypothenuse / radius * 100.0; + + // LUMINOSITY (0.0d to 100.0d) + // the higher the saturation the lower the max luminosity + double maxLuminosity = 100.0d - 0.5d * saturation; + // luminosity not bigger than max luminosity + double luminosity = (_radialLumPicker.Height - _radialLumPickerThumb.TranslationY) / _radialLumPicker.Height * maxLuminosity; + + // SETTING PICKED COLOR + double technicalHue = ColorNumberHelper.FromTargetToSourceHue(hue); + double technicalSaturation = ColorNumberHelper.FromTargetToSourceSaturation(saturation); + double technicalLuminosity = ColorNumberHelper.FromTargetToSourceLuminosity(luminosity); + this.PickedColor = Color.FromHsla(technicalHue, technicalSaturation, technicalLuminosity); + this.PickedColorForHSL = Color.FromHsla(PickedColor.Hue, 1.0d, 0.5d); + } + + private double calculateAngleClockwise(double xDiffCenter, double yDiffCenter) + { + double angle = Math.Atan2(xDiffCenter, yDiffCenter) * 180.0 / Math.PI; + if (angle < 0) + { + angle = 360.0 + angle; + } + return angle; + } + } +} \ No newline at end of file diff --git a/src/TemplateUI/Controls/ColorPicker/GradientColorStackMode.cs b/src/TemplateUI/Controls/ColorPicker/GradientColorStackMode.cs new file mode 100644 index 0000000..1d39e14 --- /dev/null +++ b/src/TemplateUI/Controls/ColorPicker/GradientColorStackMode.cs @@ -0,0 +1,15 @@ +using System; +namespace TemplateUI.Controls +{ + public enum GradientColorStackMode + { + ToRight, + ToLeft, + ToTop, + ToBottom, + ToTopLeft, + ToTopRight, + ToBottomLeft, + ToBottomRight + } +} diff --git a/src/TemplateUI/Controls/ColorPicker/GradientLayout.cs b/src/TemplateUI/Controls/ColorPicker/GradientLayout.cs new file mode 100644 index 0000000..358332d --- /dev/null +++ b/src/TemplateUI/Controls/ColorPicker/GradientLayout.cs @@ -0,0 +1,38 @@ +using System; +using Xamarin.Forms; + +namespace TemplateUI.Controls +{ + public class GradientLayout : AbsoluteLayout + { + public static readonly BindableProperty ColorsListProperty = + BindableProperty.Create(nameof(ColorsList), typeof(string), typeof(GradientLayout), ""); + public string ColorsList { get + { + return (string)GetValue(ColorsListProperty); + } + set + { + SetValue(ColorsListProperty, value); + } + } + + public Color[] Colors + { + get + { + string[] hex = ColorsList.Split(','); + Color[] colors = new Color[hex.Length]; + + for (int i = 0; i < hex.Length; i++) + { + colors[i] = Color.FromHex(hex[i].Trim()); + } + + return colors; + } + } + + public GradientColorStackMode Mode { get; set; } + } +} diff --git a/src/TemplateUI/Controls/ColorPicker/OpacityGradientLayout.cs b/src/TemplateUI/Controls/ColorPicker/OpacityGradientLayout.cs new file mode 100644 index 0000000..e16fb6a --- /dev/null +++ b/src/TemplateUI/Controls/ColorPicker/OpacityGradientLayout.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.CompilerServices; +using Xamarin.Forms; + +namespace TemplateUI.Controls +{ + public class OpacityGradientLayout : AbsoluteLayout + { + public static BindableProperty SelectedColorProperty = + BindableProperty.Create(nameof(SelectedColor), typeof(Color), typeof(OpacityGradientLayout), Color.White, defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnColorChanged); + + static void OnColorChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is OpacityGradientLayout opacityGradientLayout) + { + opacityGradientLayout.ValueChanged?.Invoke(bindable, null); + opacityGradientLayout.SelectedColor = (Color)newValue; + //opacityGradientLayout.OnPropertyChanged(); + } + } + + public event EventHandler ValueChanged; + + public Color SelectedColor + { + get => (Color)GetValue(SelectedColorProperty); + set => SetValue(SelectedColorProperty, value); + } + + public GradientColorStackMode Mode { get; set; } + } +} diff --git a/src/TemplateUI/Controls/ColorPicker/RadialPicker.cs b/src/TemplateUI/Controls/ColorPicker/RadialPicker.cs new file mode 100644 index 0000000..60848b9 --- /dev/null +++ b/src/TemplateUI/Controls/ColorPicker/RadialPicker.cs @@ -0,0 +1,32 @@ +using System; +using Xamarin.Forms; + +namespace TemplateUI.Controls +{ + public class RadialPicker : AbsoluteLayout + { + public static BindableProperty SelectedColorProperty = + BindableProperty.Create(nameof(SelectedColor), typeof(Color), typeof(OpacityGradientLayout), Color.White, defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnColorChanged); + + static void OnColorChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is RadialPicker opacityGradientLayout) + { + opacityGradientLayout.ValueChanged?.Invoke(bindable, null); + opacityGradientLayout.SelectedColor = (Color)newValue; + } + } + + public event EventHandler ValueChanged; + + public Color SelectedColor + { + get => (Color)GetValue(SelectedColorProperty); + set => SetValue(SelectedColorProperty, value); + } + + public RadialPicker() + { + } + } +} diff --git a/src/TemplateUI/Controls/ColorPicker/RoundEffect.cs b/src/TemplateUI/Controls/ColorPicker/RoundEffect.cs new file mode 100644 index 0000000..edcb328 --- /dev/null +++ b/src/TemplateUI/Controls/ColorPicker/RoundEffect.cs @@ -0,0 +1,12 @@ +using System; +using Xamarin.Forms; + +namespace TemplateUI.Controls +{ + public class RoundEffect : RoutingEffect + { + public RoundEffect() : base($"Xamarin.{nameof(RoundEffect)}") + { + } + } +} diff --git a/src/TemplateUI/Converters/ColorNumberConverter.cs b/src/TemplateUI/Converters/ColorNumberConverter.cs new file mode 100644 index 0000000..2dce6a6 --- /dev/null +++ b/src/TemplateUI/Converters/ColorNumberConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Globalization; +using TemplateUI.Helpers; +using Xamarin.Forms; + +namespace TemplateUI.Converters +{ + public class ColorNumberConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo ci) + { + if (value != null && value is double) + { + double doubleValue = (double)value; + switch (parameter) + { + case "R": + case "G": + case "B": + return Math.Round(ColorNumberHelper.FromSourceToTargetRGB(doubleValue), 0); + case "H": + return Math.Round(ColorNumberHelper.FromSourceToTargetHue(doubleValue), 0); + case "S": + return Math.Round(ColorNumberHelper.FromSourceToTargetSaturation(doubleValue), 0); + case "L": + return Math.Round(ColorNumberHelper.FromSourceToTargetLuminosity(doubleValue), 0); + default: + return 0.0; + } + } + return 0.0; + } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo ci) + { + Console.WriteLine($"Convert Back: {value}"); + return value; + } + } +} diff --git a/src/TemplateUI/Helpers/ColorNumberHelper.cs b/src/TemplateUI/Helpers/ColorNumberHelper.cs new file mode 100644 index 0000000..85c4288 --- /dev/null +++ b/src/TemplateUI/Helpers/ColorNumberHelper.cs @@ -0,0 +1,83 @@ +using System; +namespace TemplateUI.Helpers +{ + public class ColorNumberHelper + { + readonly static int maxRGB = 255; + readonly static int maxHue = 360; + readonly static int maxSaturation = 100; + readonly static int maxLuminosity = 100; + readonly static double maxInterValue = 1.0d; + + private ColorNumberHelper() + { + // UTILITY + } + + public static double FromSourceToTargetRGB(double sourceValue) + { + return sourceValue * maxRGB; + } + + public static double FromSourceToTargetHue(double sourceValue) + { + return sourceValue * maxHue; + } + + public static double FromSourceToTargetSaturation(double sourceValue) + { + return sourceValue * maxSaturation; + } + + public static double FromSourceToTargetLuminosity(double sourceValue) + { + return sourceValue * maxLuminosity; + } + + public static double FromTargetToSourceRGB(double targetValue) + { + targetValue = targetValue > maxRGB ? maxRGB : targetValue; + return maxInterValue / maxRGB * targetValue; + } + + public static double FromTargetToSourceHue(double targetValue) + { + targetValue = targetValue > maxHue ? maxHue : targetValue; + return maxInterValue / maxHue * targetValue; + } + + public static double FromTargetToSourceSaturation(double targetValue) + { + targetValue = targetValue > maxSaturation ? maxSaturation : targetValue; + return maxInterValue / maxSaturation * targetValue; + } + + public static double FromTargetToSourceLuminosity(double targetValue) + { + targetValue = targetValue > maxLuminosity ? maxLuminosity : targetValue; + return maxInterValue / maxLuminosity * targetValue; + } + + public static double MaxSaturationFromLuminosity(double luminosity) + { + if (luminosity <= maxLuminosity / 2) + { + return maxSaturation; + } + + double saturationSubtrahend = (luminosity - maxLuminosity / 2) / (maxLuminosity - maxLuminosity / 2) * maxSaturation; + return maxSaturation - saturationSubtrahend; + } + + public static double MaxLuminosityFromSaturation(double saturation) + { + return -0.5 * saturation + maxLuminosity; + } + + public static double ConvertDegreesToRadians(double degrees) + { + double radians = (Math.PI / 180) * degrees; + return radians; + } + } +} diff --git a/src/TemplateUI/TemplateUI.csproj b/src/TemplateUI/TemplateUI.csproj index 2b65112..48d2451 100644 --- a/src/TemplateUI/TemplateUI.csproj +++ b/src/TemplateUI/TemplateUI.csproj @@ -14,10 +14,14 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all + + + + diff --git a/src/TemplateUI/Themes/Generic.xaml b/src/TemplateUI/Themes/Generic.xaml index 70fa52c..e8212a8 100644 --- a/src/TemplateUI/Themes/Generic.xaml +++ b/src/TemplateUI/Themes/Generic.xaml @@ -18,6 +18,7 @@ + diff --git a/src/TemplateUI/Themes/Styles/ColorPicker.xaml b/src/TemplateUI/Themes/Styles/ColorPicker.xaml new file mode 100644 index 0000000..4041b8b --- /dev/null +++ b/src/TemplateUI/Themes/Styles/ColorPicker.xaml @@ -0,0 +1,180 @@ + + + + + diff --git a/src/TemplateUI/Themes/Styles/ColorPicker.xaml.cs b/src/TemplateUI/Themes/Styles/ColorPicker.xaml.cs new file mode 100644 index 0000000..2cab147 --- /dev/null +++ b/src/TemplateUI/Themes/Styles/ColorPicker.xaml.cs @@ -0,0 +1,12 @@ +using Xamarin.Forms; + +namespace TemplateUI.Themes +{ + public partial class ColorPicker : ResourceDictionary + { + public ColorPicker() + { + InitializeComponent(); + } + } +}