Last active
August 6, 2025 06:32
-
-
Save softlion/481cb546cf3b2f74cda136cfa1102856 to your computer and use it in GitHub Desktop.
Maui color picker using GraphicsView (no SkiaSharp)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
xmlns:vm="clr-namespace:Vapolia.OustGame.ViewModels" | |
xmlns:controls="clr-namespace:Vapolia.OustGame.Views.Controls" | |
x:Class="Vapolia.OustGame.Views.ColorPickerPopup" | |
x:DataType="vm:ColorPickerPopupViewModel" | |
BackgroundColor="Transparent" | |
NavigationPage.HasNavigationBar="False"> | |
<Grid BackgroundColor="#80000000"> | |
<Grid.GestureRecognizers> | |
<TapGestureRecognizer Command="{Binding BackgroundTappedCommand}" /> | |
</Grid.GestureRecognizers> | |
<!-- Popup content container --> | |
<Border x:Name="PopupContainer" | |
BackgroundColor="White" | |
StrokeShape="RoundRectangle 15" Stroke="Gray" StrokeThickness="1" | |
Padding="20" | |
HorizontalOptions="Center" VerticalOptions="Center" | |
WidthRequest="350" HeightRequest="320" | |
Scale="0"> | |
<!-- Prevent tap events from bubbling to background --> | |
<Border.GestureRecognizers> | |
<TapGestureRecognizer /> | |
</Border.GestureRecognizers> | |
<VerticalStackLayout Spacing="15"> | |
<!-- Color preview --> | |
<Border HorizontalOptions="Center" | |
WidthRequest="100" HeightRequest="40" | |
StrokeShape="RoundRectangle 8" Stroke="Gray" StrokeThickness="1" | |
BackgroundColor="{Binding SelectedColor}" /> | |
<controls:MauiColorPicker x:Name="ColorPicker" | |
WidthRequest="300" HeightRequest="180" | |
HorizontalOptions="Center" BackgroundColor="Transparent" | |
SpectrumType="SingleColor" | |
PickerRatio="1" | |
SpectrumRadius="15" SpectrumRect="10,10,220,160" | |
BarVertical="True" BarRect="250, 10, 20, 160" BarRadius="7" | |
ColorData="{Binding ColorData}" /> | |
</VerticalStackLayout> | |
</Border> | |
</Grid> | |
</ContentPage> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Vapolia.OustGame.Views; | |
public partial class ColorPickerPopup : ContentPage | |
{ | |
private readonly ColorPickerPopupViewModel viewModel; | |
public ColorPickerPopup(ColorPickerPopupViewModel viewModel) | |
{ | |
BindingContext = this.viewModel = viewModel; | |
InitializeComponent(); | |
} | |
protected override async void OnAppearing() | |
{ | |
base.OnAppearing(); | |
if(PopupContainer.Scale == 0) | |
await AnimatePopIn(); | |
} | |
private async Task AnimatePopIn() | |
{ | |
await PopupContainer.ScaleTo(1.0, 300, Easing.CubicOut); | |
//await PopupContainer.ScaleTo(1.0, 100, Easing.CubicInOut); | |
} | |
private async Task AnimatePopOut() | |
{ | |
await PopupContainer.ScaleTo(0, 150, Easing.CubicIn); | |
} | |
public static async Task<Color?> ShowAsync(INavigation navigation, Color initialColor) | |
{ | |
var tcs = new TaskCompletionSource<Color?>(); | |
var popup = new ColorPickerPopup(new ColorPickerPopupViewModel(initialColor, tcs)); | |
await navigation.PushModalAsync(popup); | |
var result = await tcs.Task; | |
await popup.AnimatePopOut(); | |
await navigation.PopModalAsync(); | |
return result; | |
} | |
protected override bool OnBackButtonPressed() | |
{ | |
viewModel.BackgroundTappedCommand.Execute(null); | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Vapolia.OustGame.Views.Controls; | |
namespace Vapolia.OustGame.ViewModels; | |
public partial class ColorPickerPopupViewModel : ObservableObject | |
{ | |
private readonly TaskCompletionSource<Color?> taskCompletionSource; | |
[ObservableProperty] | |
public partial Color InitialColor { get; set; } | |
[ObservableProperty] | |
public partial Color SelectedColor { get; set; } | |
public ColorDataLinked ColorData { get; } | |
public ColorPickerPopupViewModel(Color initialColor, TaskCompletionSource<Color?> tcs) | |
{ | |
taskCompletionSource = tcs; | |
InitialColor = SelectedColor = initialColor; | |
ColorData = new ColorDataLinked(initialColor); | |
ColorData.PropertyChanged += (sender, args) => | |
{ | |
if (args.PropertyName == null) | |
SelectedColor = ColorData.PickedColor; | |
}; | |
} | |
[RelayCommand] | |
private void BackgroundTapped() => taskCompletionSource.SetResult(SelectedColor); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using MauiGestures; | |
namespace Vapolia.OustGame.Views.Controls; | |
//Migrated from https://github.com/ss1969/Maui.Color.Picker | |
using System.ComponentModel; | |
using System.Globalization; | |
using System.Runtime.CompilerServices; | |
// Range: | |
// H : 0~359 | |
// S : 0~100 | |
// V : 0~100 | |
// | |
// R : 0~255 | |
// G : 0~255 | |
// B : 0~255 | |
// A : 0~255 | |
// | |
// TODO: | |
// speed optimize (use dirty rect ?) | |
// parameter change cannot correctly draw | |
// add color name float label | |
// 2 Color Pickers will interfere | |
public record ColorDataLinked : INotifyPropertyChanged | |
{ | |
// Only 1 notify for all related properties update to UI, no separate properties, | |
// that will cause unhandlable loop notification of set. | |
// use basic property syntax for value clamp, value sync, fastly update. | |
public event PropertyChangedEventHandler? PropertyChanged; | |
private void NotifyPropertyChanged( string? propertyName = null ) | |
=> PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) ); | |
//public event Action<string?>? ParameterChanged; | |
private Color pickedColor; | |
private string pickedColorHex; | |
private byte red; | |
private byte green; | |
private byte blue; | |
private int hue; | |
private int sat; | |
private int val; | |
public bool Updating { get; set; } | |
public Color PickedColor | |
{ | |
get => pickedColor; | |
set | |
{ | |
if (value != null && !value.Equals(pickedColor)) | |
{ | |
pickedColor = value; | |
SyncAndNotify(); | |
} | |
} | |
} | |
public string PickedColorHex | |
{ | |
get => pickedColorHex; | |
set | |
{ | |
if ( pickedColorHex != value ) | |
{ | |
pickedColorHex = value; | |
if (pickedColorHex.Length == 7) | |
SyncAndNotify(); | |
} | |
} | |
} | |
public byte Red | |
{ | |
get => red; | |
set | |
{ | |
if ( red != value ) | |
{ | |
red = value; | |
SyncAndNotify(); | |
} | |
} | |
} | |
public byte Green | |
{ | |
get => green; | |
set | |
{ | |
if ( green != value ) | |
{ | |
green = value; | |
SyncAndNotify(); | |
} | |
} | |
} | |
public byte Blue | |
{ | |
get => blue; | |
set | |
{ | |
if ( blue != value ) | |
{ | |
blue = value; | |
SyncAndNotify(); | |
} | |
} | |
} | |
public int Hue | |
{ | |
get => hue; | |
set | |
{ | |
value = value > 359 ? 359 : value < 0 ? 0 : value; | |
if ( hue != value ) | |
{ | |
hue = value; | |
SyncAndNotify(); | |
} | |
} | |
} | |
public int Sat | |
{ | |
get => sat; | |
set | |
{ | |
value = value > 100 ? 100 : value < 0 ? 0 : value; | |
if ( sat != value ) | |
{ | |
sat = value; | |
SyncAndNotify(); | |
} | |
} | |
} | |
public int Val | |
{ | |
get => val; | |
set | |
{ | |
value = value > 100 ? 100 : value < 0 ? 0 : value; | |
if ( val != value ) | |
{ | |
val = value; | |
SyncAndNotify(); | |
} | |
} | |
} | |
public ColorDataLinked() | |
{ | |
hue = 0; | |
sat = 100; | |
val = 100; | |
red = 255; | |
green = 0; | |
blue = 0; | |
pickedColor = PickedColor = Color.FromRgb( red, green, blue ); | |
pickedColorHex = PickedColorHex = "#FF0000"; | |
Updating = false; | |
} | |
public ColorDataLinked(Color initialColor) : this() | |
{ | |
PickedColor = initialColor; | |
} | |
public ColorDataLinked(string initialColor) : this() | |
{ | |
PickedColorHex = initialColor; | |
} | |
// Sync every value if one changes | |
public void SyncAndNotify([CallerMemberName] string? propertyName = null) | |
{ | |
if (Updating) | |
return; | |
switch (propertyName) | |
{ | |
case nameof(Red): | |
case nameof(Green): | |
case nameof(Blue): | |
pickedColor = Color.FromRgb( red, green, blue ); | |
pickedColorHex = pickedColor.ToHex(); | |
UpdateHsv(); | |
break; | |
case nameof(Hue): | |
case nameof(Sat): | |
case nameof(Val): | |
pickedColor = Color.FromHsv( hue / 359F, sat / 100F, val / 100F ); | |
pickedColorHex = pickedColor.ToHex(); | |
pickedColor.ToRgb( out red, out green, out blue ); | |
break; | |
case nameof(PickedColor): | |
pickedColorHex = pickedColor.ToHex(); | |
pickedColor.ToRgb( out red, out green, out blue ); | |
UpdateHsv(); | |
break; | |
case nameof(PickedColorHex): | |
UpdateByHex(); | |
pickedColor.ToRgb( out red, out green, out blue ); | |
UpdateHsv(); | |
break; | |
} | |
// Notify View to update value | |
NotifyPropertyChanged(); | |
//ParameterChanged?.Invoke(propertyName); | |
} | |
private void UpdateHsv() | |
{ | |
pickedColor.ToHsl( out var h, out var s, out var l ); | |
hue = (int)( h * 359 ); | |
sat = (int)( s * 100 ); | |
val = (int)( l * 100 ); | |
} | |
private void UpdateByHex() | |
{ | |
try | |
{ | |
red = byte.Parse( pickedColorHex[1..3], NumberStyles.HexNumber ); | |
green = byte.Parse( pickedColorHex[3..5], NumberStyles.HexNumber ); | |
blue = byte.Parse( pickedColorHex[5..7], NumberStyles.HexNumber ); | |
pickedColor = Color.FromRgb( red, green, blue ); | |
} | |
catch (ArgumentException) | |
{ | |
} | |
} | |
} | |
public enum SpectrumType | |
{ | |
FullColor, | |
SingleColor | |
} | |
public class MauiColorPicker : GraphicsView, IDrawable | |
{ | |
public static readonly BindableProperty ColorDataProperty = BindableProperty.Create( nameof( ColorData ), typeof( ColorDataLinked ), typeof( MauiColorPicker ), new ColorDataLinked(), propertyChanged: ColorDataChanged); | |
public static readonly BindableProperty PickerPosProperty = BindableProperty.Create( nameof( PickerPos ), typeof( Point ), typeof( MauiColorPicker ), Point.Zero); | |
public static readonly BindableProperty PickerRatioProperty = BindableProperty.Create( nameof( PickerRatio ), typeof( float ), typeof( MauiColorPicker ), 1f); | |
public static readonly BindableProperty SpectrumTypeProperty = BindableProperty.Create( nameof( SpectrumType ), typeof( SpectrumType ), typeof( MauiColorPicker ), SpectrumType.FullColor, BindingMode.OneTime); | |
public static readonly BindableProperty SpectrumRectProperty = BindableProperty.Create( nameof( SpectrumRect ), typeof( RectF ), typeof( MauiColorPicker ), new RectF( 0, 0, 100, 100 )); | |
public static readonly BindableProperty SpectrumRadiusProperty = BindableProperty.Create( nameof( SpectrumRadius ), typeof( float ), typeof( MauiColorPicker ), 0f); | |
public static readonly BindableProperty BarRectProperty = BindableProperty.Create( nameof( BarRect ), typeof( RectF ), typeof( MauiColorPicker ), new RectF( 0, 0, 100, 20 )); | |
public static readonly BindableProperty BarVerticalProperty = BindableProperty.Create( nameof( BarVertical ), typeof( bool ), typeof( MauiColorPicker ), false); | |
public static readonly BindableProperty BarRadiusProperty = BindableProperty.Create( nameof( BarRadius ), typeof( float ), typeof( MauiColorPicker ), 0f); | |
#region Bindable Properties | |
public ColorDataLinked ColorData { get => (ColorDataLinked)GetValue( ColorDataProperty ); set => SetValue( ColorDataProperty, value ); } | |
public Point PickerPos { get => (Point)GetValue( PickerPosProperty ); set => SetValue( PickerPosProperty, value ); } | |
public float PickerRatio { get => (float)GetValue( PickerRatioProperty ); set => SetValue( PickerRatioProperty, value ); } | |
public SpectrumType SpectrumType { get => (SpectrumType)GetValue( SpectrumTypeProperty ); set => SetValue( SpectrumTypeProperty, value ); } | |
public RectF SpectrumRect { get => (RectF)GetValue( SpectrumRectProperty ); set => SetValue( SpectrumRectProperty, value ); } | |
public float SpectrumRadius { get => (float)GetValue( SpectrumRadiusProperty ); set => SetValue( SpectrumRadiusProperty, value ); } | |
public RectF BarRect { get => (RectF)GetValue( BarRectProperty ); set => SetValue( BarRectProperty, value ); } | |
public bool BarVertical { get => (bool)GetValue( BarVerticalProperty ); set => SetValue( BarVerticalProperty, value ); } | |
public float BarRadius { get => (float)GetValue( BarRadiusProperty ); set => SetValue( BarRadiusProperty, value ); } | |
#endregion | |
private static void ColorDataChanged(BindableObject bindable, object oldValue, object newValue) | |
{ | |
if (bindable is MauiColorPicker colorPicker) | |
colorPicker.ColorDataChanged((ColorDataLinked?)oldValue, (ColorDataLinked?)newValue); | |
} | |
private void ColorDataChanged(ColorDataLinked? oldValue, ColorDataLinked? newValue) | |
{ | |
//oldValue?.ParameterChanged -= ColorData_ParameterChanged; | |
//newValue?.ParameterChanged += ColorData_ParameterChanged; | |
oldValue?.PropertyChanged -= ColorData_ParameterChanged; | |
newValue?.PropertyChanged += ColorData_ParameterChanged; | |
ColorData_ParameterChanged(this, new PropertyChangedEventArgs(null)); | |
} | |
// Buffers for Spectrum & bar | |
private PictureCanvas? barBuffer; | |
private IPicture? barPicture; | |
private PointF barPickerPos; | |
// true to create new bar picture buffer | |
private bool updateBar = true; | |
private PictureCanvas? spectrumBuffer; | |
private IPicture? spectrumPicture; | |
private PointF spectrumPickerPos; | |
// true to create new spectrum picture buffer | |
private bool updateSpectrum = true; | |
private bool pointerDown; | |
// 0 : not down in spectrum or bar rect. 1: down in spectrumRect, 2: down in bar rect. | |
private int pointerDownFrom; | |
public MauiColorPicker() | |
{ | |
Drawable = this; | |
//Pointer | |
var pointerGestureRecognizer = new PointerGestureRecognizer(); | |
pointerGestureRecognizer.PointerPressed += PointerGestureRecognizer_PointerPressed; | |
pointerGestureRecognizer.PointerMoved += PointerGestureRecognizer_PointerMoved; | |
pointerGestureRecognizer.PointerReleased += PointerGestureRecognizer_PointerReleased; | |
GestureRecognizers.Add(pointerGestureRecognizer); | |
//Touch | |
Gesture.SetIsPanImmediate(this, true); | |
Gesture.SetPanPointCommand(this, new Command<PointEventArgs>(args => | |
{ | |
var point = (PointF)args.Point; | |
if(SpectrumRect.Contains(point)) | |
pointerDownFrom = 1; | |
else if(BarRect.Contains(point)) | |
pointerDownFrom = 2; | |
CalculateTapPosition(args.Point); | |
})); | |
ColorDataChanged(null, ColorData); | |
} | |
//private void ColorData_ParameterChanged(string? propertyName) | |
private void ColorData_ParameterChanged(object? _, PropertyChangedEventArgs __) | |
{ | |
UpdatePickerPosByColor(); | |
updateSpectrum = true; | |
updateBar = true; | |
//pointerDown = false; | |
Invalidate(); | |
} | |
#region Pointer gesture | |
private void PointerGestureRecognizer_PointerPressed( object? sender, PointerEventArgs e ) | |
{ | |
if ( !pointerDown ) | |
{ | |
var clickPosition = e.GetPosition( this ); | |
if ( clickPosition == null ) return; | |
if ( SpectrumRect.Contains( clickPosition.Value ) ) | |
pointerDownFrom = 1; | |
else if ( BarRect.Contains( clickPosition.Value ) ) | |
pointerDownFrom = 2; | |
else | |
pointerDownFrom = 0; | |
CalculateTapPosition( clickPosition.Value ); | |
} | |
pointerDown = true; | |
} | |
private void PointerGestureRecognizer_PointerMoved( object? sender, PointerEventArgs e ) | |
{ | |
if ( !pointerDown ) return; | |
var clickPosition = e.GetPosition( this ); | |
if ( clickPosition == null ) return; | |
CalculateTapPosition( clickPosition.Value ); | |
} | |
private void PointerGestureRecognizer_PointerReleased( object? sender, PointerEventArgs e ) | |
{ | |
if ( pointerDown ) | |
{ | |
var clickPosition = e.GetPosition( this ); | |
if ( clickPosition == null ) return; | |
CalculateTapPosition( clickPosition.Value ); | |
} | |
pointerDown = false; | |
pointerDownFrom = 0; | |
} | |
#endregion | |
/// <summary> | |
/// Monitor properties and ask for redraw | |
/// </summary> | |
protected override void OnPropertyChanged(string? propertyName = null) | |
{ | |
base.OnPropertyChanged(propertyName); | |
switch(propertyName) | |
{ | |
case nameof(WidthRequest): | |
case nameof(HeightRequest): | |
case nameof(BackgroundColor): | |
case nameof(PickerRatio): | |
case nameof(ColorData): | |
break; | |
case nameof(SpectrumType): | |
updateSpectrum = true; | |
updateBar = true; | |
break; | |
case nameof(SpectrumRadius): | |
case nameof(SpectrumRect): | |
spectrumPickerPos = new PointF( SpectrumRect.X, SpectrumRect.Y ); | |
updateSpectrum = true; | |
break; | |
case nameof(BarRect): | |
case nameof(BarVertical): | |
case nameof(BarRadius): | |
barPickerPos = new PointF(BarRect.X, BarRect.Y); | |
updateBar = true; | |
break; | |
default: | |
return; | |
} | |
// Init redraw | |
Invalidate(); | |
} | |
private void CalculateTapPosition( Point clickPosition ) | |
{ | |
//Outside | |
if (pointerDownFrom == 0) | |
return; | |
//Inside spectrum | |
if(pointerDownFrom == 1 && !SpectrumRect.Contains( clickPosition )) | |
return; | |
//Inside bar | |
if(pointerDownFrom == 2 && !BarRect.Contains( clickPosition )) | |
return; | |
//var d = ( clickPosition - _prev ); | |
//if (Math.Abs(d.Width) + Math.Abs(d.Height) < 5 ) return; | |
//_prev = clickPosition; | |
// we update manually later | |
ColorData.Updating = true; | |
if ( pointerDownFrom == 1 ) | |
spectrumPickerPos = clickPosition; | |
else if ( pointerDownFrom == 2 ) | |
{ | |
barPickerPos = clickPosition; | |
// align picker to bar center | |
if (BarVertical) | |
barPickerPos.X = BarRect.X + BarRect.Width / 2; | |
else | |
barPickerPos.Y = BarRect.Y + BarRect.Height / 2; | |
} | |
switch (SpectrumType) | |
{ | |
case SpectrumType.FullColor: | |
{ | |
var val = BarVertical ? ( BarRect.Height - barPickerPos.Y + BarRect.Y ) / BarRect.Height | |
: ( BarRect.Width - barPickerPos.X + BarRect.X ) / BarRect.Width; | |
ColorData.Val = (int)( val * 100 ); | |
// Horizontal => Hue 0 to 359 | |
ColorData.Hue = (int)( ( spectrumPickerPos.X - SpectrumRect.X ) * 359 / SpectrumRect.Width ); | |
// Vertical => Sat 100:0 | |
ColorData.Sat = (int)( ( SpectrumRect.Height - ( spectrumPickerPos.Y - SpectrumRect.Y ) ) * 100 / SpectrumRect.Height ); | |
updateBar = true; | |
updateSpectrum = true; | |
break; | |
} | |
case SpectrumType.SingleColor: | |
{ | |
var hueValue = BarVertical ? ( barPickerPos.Y - BarRect.Y ) / BarRect.Height | |
: ( barPickerPos.X - BarRect.X ) / BarRect.Width; | |
ColorData.Hue = (int)( hueValue * 359 ); | |
// Horizontal => Sat 0:100 | |
ColorData.Sat = (int)( ( spectrumPickerPos.X - SpectrumRect.X ) * 100 / SpectrumRect.Width ); | |
// Vertical => Value 100:0 | |
ColorData.Val = (int)( ( SpectrumRect.Height - ( spectrumPickerPos.Y - SpectrumRect.Y ) ) * 100 / SpectrumRect.Height ); | |
updateSpectrum = true; | |
break; | |
} | |
} | |
// Done updating ColorData | |
ColorData.Updating = false; | |
//Update internal data + notify with a null parameter | |
ColorData.SyncAndNotify(nameof(ColorData.Hue)); | |
//OnPropertyChanged(nameof(ColorData)); | |
} | |
private void UpdatePickerPosByColor() | |
{ | |
if ( SpectrumType == SpectrumType.FullColor ) | |
{ | |
spectrumPickerPos.X = ( ColorData.Hue / 359f * SpectrumRect.Width ) + SpectrumRect.X; | |
spectrumPickerPos.Y = ( SpectrumRect.Height - ColorData.Sat / 100F * SpectrumRect.Height ) + SpectrumRect.Y; | |
if ( BarVertical ) | |
{ | |
barPickerPos.X = BarRect.X + BarRect.Width / 2; | |
barPickerPos.Y = ColorData.Val / 100F * BarRect.Height + BarRect.Y; | |
} | |
else | |
{ | |
barPickerPos.X = ColorData.Val / 100F * BarRect.Width + BarRect.X; | |
barPickerPos.Y = BarRect.Y + BarRect.Height / 2; | |
} | |
} | |
else if ( SpectrumType == SpectrumType.SingleColor ) | |
{ | |
spectrumPickerPos.X = ( ColorData.Sat / 100F * SpectrumRect.Width ) + SpectrumRect.X; | |
spectrumPickerPos.Y = ( SpectrumRect.Height - ColorData.Val / 100F * SpectrumRect.Height ) + SpectrumRect.Y; | |
if ( BarVertical ) | |
{ | |
barPickerPos.X = BarRect.X + BarRect.Width / 2; | |
barPickerPos.Y = ColorData.Hue / 359F * BarRect.Height + BarRect.Y; | |
} | |
else | |
{ | |
barPickerPos.X = ColorData.Hue / 359F * BarRect.Width + BarRect.X; | |
barPickerPos.Y = BarRect.Y + BarRect.Height / 2; | |
} | |
} | |
} | |
#region Draw | |
public void Draw( ICanvas canvas, RectF dirtyRectF ) | |
{ | |
canvas.ResetState(); | |
DrawBackground(canvas, dirtyRectF); | |
if (dirtyRectF.IntersectsWith(BarRect)) | |
{ | |
DrawBarRect(canvas); | |
DrawBarPicker(canvas); | |
} | |
var pickerRadius = 6 * PickerRatio; | |
dirtyRectF.Inflate(pickerRadius, pickerRadius); | |
if (dirtyRectF.IntersectsWith(SpectrumRect)) | |
{ | |
DrawSpectrumRect(canvas); | |
DrawSpectrumPicker(canvas); | |
} | |
} | |
private void DrawBackground(ICanvas canvas, RectF dirtyRect) | |
{ | |
canvas.FillColor = BackgroundColor; | |
canvas.FillRectangle( dirtyRect ); | |
} | |
private void DrawSpectrumRect(ICanvas canvas) | |
{ | |
// if need to update, draw to picture & store | |
if(updateSpectrum) | |
{ | |
updateSpectrum = false; | |
spectrumBuffer?.Dispose(); | |
spectrumBuffer = new PictureCanvas(SpectrumRect.X, SpectrumRect.Y, SpectrumRect.Width, SpectrumRect.Height) | |
{ | |
Antialias = true, | |
StrokeSize = 2, | |
StrokeColor = Black | |
}; | |
spectrumBuffer.DrawRoundedRectangle( SpectrumRect.X, SpectrumRect.Y, SpectrumRect.Width, SpectrumRect.Height, SpectrumRadius ); | |
if ( SpectrumType == SpectrumType.FullColor ) | |
{ | |
var gradientBrush = new LinearGradientBrush | |
{ | |
StartPoint = new Point( 0, 0 ), | |
EndPoint = new Point( 1, 0 ), | |
GradientStops = [ | |
new GradientStop {Color = Red, Offset=0}, | |
new GradientStop {Color = Color.FromRgb(255, 255, 0), Offset=0.1666F}, | |
new GradientStop {Color = Green, Offset=0.3333F}, | |
new GradientStop {Color = Color.FromRgb(0, 255, 255), Offset=0.5f}, | |
new GradientStop {Color = Blue, Offset=0.6666F}, | |
new GradientStop {Color = Color.FromRgb(255, 0, 255), Offset=0.8333F}, | |
], | |
}; | |
spectrumBuffer.SetFillPaint(gradientBrush, SpectrumRect); | |
spectrumBuffer.FillRoundedRectangle(SpectrumRect.X, SpectrumRect.Y, SpectrumRect.Width, SpectrumRect.Height, SpectrumRadius); | |
gradientBrush = new LinearGradientBrush | |
{ | |
StartPoint = new Point( 0, 0 ), | |
EndPoint = new Point( 0, 1 ), // Vertical, White Mask | |
GradientStops = | |
[ | |
new GradientStop { Color = White.WithAlpha(0), Offset = 0f }, // Start Color 0% White | |
new GradientStop { Color = White, Offset = 1f }, // End Color 100% White | |
] | |
}; | |
spectrumBuffer.SetFillPaint( gradientBrush, SpectrumRect ); | |
spectrumBuffer.FillRoundedRectangle( SpectrumRect.X, SpectrumRect.Y, SpectrumRect.Width, SpectrumRect.Height, SpectrumRadius ); | |
} | |
if ( SpectrumType == SpectrumType.SingleColor ) | |
{ | |
spectrumBuffer.FillColor = Color.FromHsva( ColorData.Hue / 359f, 1, 1, 1 ); | |
spectrumBuffer.FillRoundedRectangle( SpectrumRect.X, SpectrumRect.Y, SpectrumRect.Width, SpectrumRect.Height, SpectrumRadius ); | |
var gradientBrush = new LinearGradientBrush | |
{ | |
StartPoint = new Point( 0, 0 ), | |
EndPoint = new Point( 1, 0 ), // Horizontal, White Mask | |
GradientStops = | |
[ | |
new GradientStop { Color = White, Offset = 0 }, // Start Color 100% White | |
new GradientStop { Color = White.WithAlpha(0), Offset = 1 }, // End Color 0% White | |
] | |
}; | |
spectrumBuffer.SetFillPaint( gradientBrush, SpectrumRect ); | |
spectrumBuffer.FillRoundedRectangle( SpectrumRect.X, SpectrumRect.Y, SpectrumRect.Width, SpectrumRect.Height, SpectrumRadius ); | |
gradientBrush = new LinearGradientBrush | |
{ | |
StartPoint = new Point( 0, 0 ), | |
EndPoint = new Point( 0, 1 ), // Vertical, Black Mask | |
GradientStops = | |
[ | |
new GradientStop { Color = Black.WithAlpha(0), Offset = 0 }, // Start Color 0% Black | |
new GradientStop { Color = Black, Offset = 1 }, // End Color 100% Black | |
], | |
}; | |
spectrumBuffer.SetFillPaint( gradientBrush, SpectrumRect ); | |
spectrumBuffer.FillRoundedRectangle( SpectrumRect.X, SpectrumRect.Y, SpectrumRect.Width, SpectrumRect.Height, SpectrumRadius ); | |
} | |
// Store | |
spectrumPicture = spectrumBuffer.Picture; | |
} | |
// Use stored buffer | |
spectrumPicture?.Draw(canvas); | |
} | |
private void DrawBarRect(ICanvas canvas) | |
{ | |
// if need to update, draw to picture and store | |
if ( updateBar ) | |
{ | |
updateBar = false; | |
barBuffer?.Dispose(); | |
barBuffer = new PictureCanvas( BarRect.X, BarRect.Y, BarRect.Width, BarRect.Height ) | |
{ | |
Antialias = true, | |
StrokeSize = 2, | |
StrokeColor = Black | |
}; | |
barBuffer.DrawRoundedRectangle( BarRect.X, BarRect.Y, BarRect.Width, BarRect.Height, BarRadius ); | |
if ( SpectrumType == SpectrumType.FullColor ) | |
{ | |
canvas.FillColor = Color.FromHsv( ColorData.Hue / 359f, 1, 1 ); | |
barBuffer.FillRoundedRectangle( BarRect.X, BarRect.Y, BarRect.Width, BarRect.Height, BarRadius ); | |
var gradientBrush = new LinearGradientBrush | |
{ | |
StartPoint = new Point( 0, 0 ), | |
EndPoint = new Point( BarVertical ? 0 : 1, BarVertical ? 1 : 0 ), | |
GradientStops = [ | |
new GradientStop { Color = Black.WithAlpha(0), Offset = 0.0f }, // Start Color 0% Black | |
new GradientStop { Color = Black, Offset = 1.0f }, // End Color 100% Black | |
], | |
}; | |
barBuffer.SetFillPaint( gradientBrush, BarRect ); | |
barBuffer.FillRoundedRectangle( BarRect.X, BarRect.Y, BarRect.Width, BarRect.Height, BarRadius ); | |
} | |
if ( SpectrumType == SpectrumType.SingleColor ) | |
{ | |
var gradientBrush = new LinearGradientBrush | |
{ | |
StartPoint = new Point( 0, 0 ), | |
EndPoint = new Point( BarVertical ? 0 : 1, BarVertical ? 1 : 0 ), | |
GradientStops = [ | |
new GradientStop {Color = Red, Offset=0f}, | |
new GradientStop {Color = Color.FromRgb(255, 255, 0), Offset=0.1666f}, | |
new GradientStop {Color = Green, Offset=0.3333f}, | |
new GradientStop {Color = Color.FromRgb(0, 255, 255), Offset=0.5f}, | |
new GradientStop {Color = Blue, Offset=0.6666f}, | |
new GradientStop {Color = Color.FromRgb(255, 0, 255), Offset=0.8333f}, | |
], | |
}; | |
barBuffer.SetFillPaint( gradientBrush, BarRect ); | |
barBuffer.FillRoundedRectangle( BarRect.X, BarRect.Y, BarRect.Width, BarRect.Height, BarRadius ); | |
} | |
// store | |
barPicture = barBuffer.Picture; | |
} | |
// use stored buffer | |
barPicture?.Draw( canvas ); | |
} | |
private void DrawSpectrumPicker(ICanvas canvas) | |
{ | |
canvas.StrokeSize = 3 * PickerRatio; | |
canvas.StrokeColor = ColorData.Red + ColorData.Green + ColorData.Blue < 382 ? White : Black; | |
canvas.DrawCircle( spectrumPickerPos, 6 * PickerRatio ); | |
} | |
private void DrawBarPicker(ICanvas canvas) | |
{ | |
canvas.StrokeSize = 8 * PickerRatio; | |
canvas.StrokeColor = Color.FromRgb( 255, 255, 255 ); | |
canvas.FillColor = Color.FromRgb( 0, 0, 0 ); | |
RectF indicator; | |
if (BarVertical) | |
indicator = new( | |
BarRect.X, | |
barPickerPos.Y - 5 * PickerRatio, // make it center | |
BarRect.Width, | |
10 * PickerRatio); | |
else | |
indicator = new( | |
barPickerPos.X - 5 * PickerRatio, // make it center | |
BarRect.Y, | |
10 * PickerRatio, | |
BarRect.Height); | |
canvas.DrawRoundedRectangle( indicator, 20 ); | |
canvas.FillRoundedRectangle( indicator, 20 ); | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment