Switch
This commit is contained in:
@@ -70,13 +70,12 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
// TrackColor sets both On and Off track colors
|
// TrackColor sets the On track color (MAUI's OnColor)
|
||||||
if (@switch.TrackColor is not null)
|
if (@switch.TrackColor is not null)
|
||||||
{
|
{
|
||||||
var color = @switch.TrackColor.ToSKColor();
|
handler.PlatformView.OnTrackColor = @switch.TrackColor;
|
||||||
handler.PlatformView.OnTrackColor = color;
|
// Off track is a lighter/desaturated version
|
||||||
// Off track could be a lighter version
|
handler.PlatformView.OffTrackColor = @switch.TrackColor.WithAlpha(0.5f);
|
||||||
handler.PlatformView.OffTrackColor = color.WithAlpha(128);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (@switch.ThumbColor is not null)
|
if (@switch.ThumbColor is not null)
|
||||||
handler.PlatformView.ThumbColor = @switch.ThumbColor.ToSKColor();
|
handler.PlatformView.ThumbColor = @switch.ThumbColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
||||||
@@ -94,6 +93,7 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
|||||||
|
|
||||||
if (@switch.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (@switch.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
|
// Background color for the switch container (not the track)
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,122 +3,115 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform;
|
namespace Microsoft.Maui.Platform;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skia-rendered toggle switch control with full XAML styling support.
|
/// Skia-rendered toggle switch control with full MAUI compliance.
|
||||||
|
/// Implements ISwitch interface requirements:
|
||||||
|
/// - IsOn property with Toggled event
|
||||||
|
/// - OnColor (TrackColor when on)
|
||||||
|
/// - ThumbColor
|
||||||
|
/// - Smooth animation on toggle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SkiaSwitch : SkiaView
|
public class SkiaSwitch : SkiaView
|
||||||
{
|
{
|
||||||
|
#region SKColor Helper
|
||||||
|
|
||||||
|
private static SKColor ToSKColor(Color? color)
|
||||||
|
{
|
||||||
|
if (color == null) return SKColors.Transparent;
|
||||||
|
return new SKColor(
|
||||||
|
(byte)(color.Red * 255),
|
||||||
|
(byte)(color.Green * 255),
|
||||||
|
(byte)(color.Blue * 255),
|
||||||
|
(byte)(color.Alpha * 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region BindableProperties
|
#region BindableProperties
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for IsOn.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty IsOnProperty =
|
public static readonly BindableProperty IsOnProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(IsOn),
|
nameof(IsOn),
|
||||||
typeof(bool),
|
typeof(bool),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
false,
|
false,
|
||||||
BindingMode.OneWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).OnIsOnChanged());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).OnIsOnChanged((bool)o, (bool)n));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for OnTrackColor.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty OnTrackColorProperty =
|
public static readonly BindableProperty OnTrackColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(OnTrackColor),
|
nameof(OnTrackColor),
|
||||||
typeof(SKColor),
|
typeof(Color),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
new SKColor(33, 150, 243),
|
Color.FromRgb(33, 150, 243), // Material Blue
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for OffTrackColor.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty OffTrackColorProperty =
|
public static readonly BindableProperty OffTrackColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(OffTrackColor),
|
nameof(OffTrackColor),
|
||||||
typeof(SKColor),
|
typeof(Color),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
new SKColor(158, 158, 158),
|
Color.FromRgb(158, 158, 158),
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for ThumbColor.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty ThumbColorProperty =
|
public static readonly BindableProperty ThumbColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(ThumbColor),
|
nameof(ThumbColor),
|
||||||
typeof(SKColor),
|
typeof(Color),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
SKColors.White,
|
Colors.White,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for DisabledColor.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty DisabledColorProperty =
|
public static readonly BindableProperty DisabledColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(DisabledColor),
|
nameof(DisabledColor),
|
||||||
typeof(SKColor),
|
typeof(Color),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
new SKColor(189, 189, 189),
|
Color.FromRgb(189, 189, 189),
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for TrackWidth.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty TrackWidthProperty =
|
public static readonly BindableProperty TrackWidthProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(TrackWidth),
|
nameof(TrackWidth),
|
||||||
typeof(float),
|
typeof(double),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
52f,
|
52.0,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).InvalidateMeasure());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for TrackHeight.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty TrackHeightProperty =
|
public static readonly BindableProperty TrackHeightProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(TrackHeight),
|
nameof(TrackHeight),
|
||||||
typeof(float),
|
typeof(double),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
32f,
|
32.0,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).InvalidateMeasure());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for ThumbRadius.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty ThumbRadiusProperty =
|
public static readonly BindableProperty ThumbRadiusProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(ThumbRadius),
|
nameof(ThumbRadius),
|
||||||
typeof(float),
|
typeof(double),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
12f,
|
12.0,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for ThumbPadding.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty ThumbPaddingProperty =
|
public static readonly BindableProperty ThumbPaddingProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(ThumbPadding),
|
nameof(ThumbPadding),
|
||||||
typeof(float),
|
typeof(double),
|
||||||
typeof(SkiaSwitch),
|
typeof(SkiaSwitch),
|
||||||
4f,
|
4.0,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSwitch)b).Invalidate());
|
||||||
|
|
||||||
@@ -136,114 +129,203 @@ public class SkiaSwitch : SkiaView
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the on track color.
|
/// Gets or sets the track color when the switch is on.
|
||||||
|
/// This is the primary MAUI Switch.OnColor property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SKColor OnTrackColor
|
public Color OnTrackColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(OnTrackColorProperty);
|
get => (Color)GetValue(OnTrackColorProperty);
|
||||||
set => SetValue(OnTrackColorProperty, value);
|
set => SetValue(OnTrackColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the off track color.
|
/// Gets or sets the track color when the switch is off.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SKColor OffTrackColor
|
public Color OffTrackColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(OffTrackColorProperty);
|
get => (Color)GetValue(OffTrackColorProperty);
|
||||||
set => SetValue(OffTrackColorProperty, value);
|
set => SetValue(OffTrackColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the thumb color.
|
/// Gets or sets the thumb color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SKColor ThumbColor
|
public Color ThumbColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(ThumbColorProperty);
|
get => (Color)GetValue(ThumbColorProperty);
|
||||||
set => SetValue(ThumbColorProperty, value);
|
set => SetValue(ThumbColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the disabled color.
|
/// Gets or sets the color used when the control is disabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SKColor DisabledColor
|
public Color DisabledColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(DisabledColorProperty);
|
get => (Color)GetValue(DisabledColorProperty);
|
||||||
set => SetValue(DisabledColorProperty, value);
|
set => SetValue(DisabledColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the track width.
|
/// Gets or sets the track width in device-independent units.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float TrackWidth
|
public double TrackWidth
|
||||||
{
|
{
|
||||||
get => (float)GetValue(TrackWidthProperty);
|
get => (double)GetValue(TrackWidthProperty);
|
||||||
set => SetValue(TrackWidthProperty, value);
|
set => SetValue(TrackWidthProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the track height.
|
/// Gets or sets the track height in device-independent units.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float TrackHeight
|
public double TrackHeight
|
||||||
{
|
{
|
||||||
get => (float)GetValue(TrackHeightProperty);
|
get => (double)GetValue(TrackHeightProperty);
|
||||||
set => SetValue(TrackHeightProperty, value);
|
set => SetValue(TrackHeightProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the thumb radius.
|
/// Gets or sets the thumb radius in device-independent units.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float ThumbRadius
|
public double ThumbRadius
|
||||||
{
|
{
|
||||||
get => (float)GetValue(ThumbRadiusProperty);
|
get => (double)GetValue(ThumbRadiusProperty);
|
||||||
set => SetValue(ThumbRadiusProperty, value);
|
set => SetValue(ThumbRadiusProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the thumb padding.
|
/// Gets or sets the thumb padding in device-independent units.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float ThumbPadding
|
public double ThumbPadding
|
||||||
{
|
{
|
||||||
get => (float)GetValue(ThumbPaddingProperty);
|
get => (double)GetValue(ThumbPaddingProperty);
|
||||||
set => SetValue(ThumbPaddingProperty, value);
|
set => SetValue(ThumbPaddingProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Animation Fields
|
||||||
|
|
||||||
private float _animationProgress;
|
private float _animationProgress;
|
||||||
|
private System.Timers.Timer? _animationTimer;
|
||||||
|
private bool _animatingToOn;
|
||||||
|
private const int AnimationDurationMs = 200;
|
||||||
|
private const int AnimationFrameMs = 16; // ~60fps
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event raised when the switch is toggled.
|
/// Event raised when the switch is toggled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<ToggledEventArgs>? Toggled;
|
public event EventHandler<ToggledEventArgs>? Toggled;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
public SkiaSwitch()
|
public SkiaSwitch()
|
||||||
{
|
{
|
||||||
IsFocusable = true;
|
IsFocusable = true;
|
||||||
|
_animationProgress = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnIsOnChanged()
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
|
||||||
|
private void OnIsOnChanged(bool oldValue, bool newValue)
|
||||||
{
|
{
|
||||||
_animationProgress = IsOn ? 1f : 0f;
|
// Start animation
|
||||||
Toggled?.Invoke(this, new ToggledEventArgs(IsOn));
|
StartAnimation(newValue);
|
||||||
SkiaVisualStateManager.GoToState(this, IsOn ? "On" : "Off");
|
|
||||||
|
Toggled?.Invoke(this, new ToggledEventArgs(newValue));
|
||||||
|
SkiaVisualStateManager.GoToState(this, newValue ? "On" : "Off");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Animation
|
||||||
|
|
||||||
|
private void StartAnimation(bool toOn)
|
||||||
|
{
|
||||||
|
_animatingToOn = toOn;
|
||||||
|
|
||||||
|
// Stop existing animation
|
||||||
|
_animationTimer?.Stop();
|
||||||
|
_animationTimer?.Dispose();
|
||||||
|
|
||||||
|
// Create new animation timer
|
||||||
|
_animationTimer = new System.Timers.Timer(AnimationFrameMs);
|
||||||
|
_animationTimer.Elapsed += OnAnimationFrame;
|
||||||
|
_animationTimer.AutoReset = true;
|
||||||
|
_animationTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnimationFrame(object? sender, System.Timers.ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
float step = AnimationFrameMs / (float)AnimationDurationMs;
|
||||||
|
|
||||||
|
if (_animatingToOn)
|
||||||
|
{
|
||||||
|
_animationProgress += step;
|
||||||
|
if (_animationProgress >= 1f)
|
||||||
|
{
|
||||||
|
_animationProgress = 1f;
|
||||||
|
StopAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_animationProgress -= step;
|
||||||
|
if (_animationProgress <= 0f)
|
||||||
|
{
|
||||||
|
_animationProgress = 0f;
|
||||||
|
StopAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request redraw on UI thread
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StopAnimation()
|
||||||
|
{
|
||||||
|
_animationTimer?.Stop();
|
||||||
|
_animationTimer?.Dispose();
|
||||||
|
_animationTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Rendering
|
||||||
|
|
||||||
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
||||||
{
|
{
|
||||||
var centerY = bounds.MidY;
|
var trackWidth = (float)TrackWidth;
|
||||||
var trackLeft = bounds.MidX - TrackWidth / 2f;
|
var trackHeight = (float)TrackHeight;
|
||||||
var trackRight = trackLeft + TrackWidth;
|
var thumbRadius = (float)ThumbRadius;
|
||||||
|
var thumbPadding = (float)ThumbPadding;
|
||||||
|
|
||||||
// Calculate thumb position
|
var centerY = bounds.MidY;
|
||||||
var thumbMinX = trackLeft + ThumbPadding + ThumbRadius;
|
var trackLeft = bounds.MidX - trackWidth / 2f;
|
||||||
var thumbMaxX = trackRight - ThumbPadding - ThumbRadius;
|
var trackRight = trackLeft + trackWidth;
|
||||||
|
|
||||||
|
// Calculate thumb position based on animation progress
|
||||||
|
var thumbMinX = trackLeft + thumbPadding + thumbRadius;
|
||||||
|
var thumbMaxX = trackRight - thumbPadding - thumbRadius;
|
||||||
var thumbX = thumbMinX + _animationProgress * (thumbMaxX - thumbMinX);
|
var thumbX = thumbMinX + _animationProgress * (thumbMaxX - thumbMinX);
|
||||||
|
|
||||||
// Interpolate track color
|
// Get colors
|
||||||
|
var onColorSK = ToSKColor(OnTrackColor);
|
||||||
|
var offColorSK = ToSKColor(OffTrackColor);
|
||||||
|
var thumbColorSK = ToSKColor(ThumbColor);
|
||||||
|
var disabledColorSK = ToSKColor(DisabledColor);
|
||||||
|
|
||||||
|
// Interpolate track color based on animation progress
|
||||||
var trackColor = IsEnabled
|
var trackColor = IsEnabled
|
||||||
? InterpolateColor(OffTrackColor, OnTrackColor, _animationProgress)
|
? InterpolateColor(offColorSK, onColorSK, _animationProgress)
|
||||||
: DisabledColor;
|
: disabledColorSK;
|
||||||
|
|
||||||
// Draw track
|
// Draw track
|
||||||
using var trackPaint = new SKPaint
|
using var trackPaint = new SKPaint
|
||||||
@@ -254,11 +336,11 @@ public class SkiaSwitch : SkiaView
|
|||||||
};
|
};
|
||||||
|
|
||||||
var trackRect = new SKRoundRect(
|
var trackRect = new SKRoundRect(
|
||||||
new SKRect(trackLeft, centerY - TrackHeight / 2f, trackRight, centerY + TrackHeight / 2f),
|
new SKRect(trackLeft, centerY - trackHeight / 2f, trackRight, centerY + trackHeight / 2f),
|
||||||
TrackHeight / 2f);
|
trackHeight / 2f);
|
||||||
canvas.DrawRoundRect(trackRect, trackPaint);
|
canvas.DrawRoundRect(trackRect, trackPaint);
|
||||||
|
|
||||||
// Draw thumb shadow
|
// Draw thumb shadow (only when enabled)
|
||||||
if (IsEnabled)
|
if (IsEnabled)
|
||||||
{
|
{
|
||||||
using var shadowPaint = new SKPaint
|
using var shadowPaint = new SKPaint
|
||||||
@@ -267,29 +349,29 @@ public class SkiaSwitch : SkiaView
|
|||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 2f)
|
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 2f)
|
||||||
};
|
};
|
||||||
canvas.DrawCircle(thumbX + 1f, centerY + 1f, ThumbRadius, shadowPaint);
|
canvas.DrawCircle(thumbX + 1f, centerY + 1f, thumbRadius, shadowPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw thumb
|
// Draw thumb
|
||||||
using var thumbPaint = new SKPaint
|
using var thumbPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = IsEnabled ? ThumbColor : new SKColor(245, 245, 245),
|
Color = IsEnabled ? thumbColorSK : new SKColor(245, 245, 245),
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill
|
Style = SKPaintStyle.Fill
|
||||||
};
|
};
|
||||||
canvas.DrawCircle(thumbX, centerY, ThumbRadius, thumbPaint);
|
canvas.DrawCircle(thumbX, centerY, thumbRadius, thumbPaint);
|
||||||
|
|
||||||
// Draw focus ring
|
// Draw focus ring
|
||||||
if (IsFocused)
|
if (IsFocused)
|
||||||
{
|
{
|
||||||
using var focusPaint = new SKPaint
|
using var focusPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = OnTrackColor.WithAlpha(60),
|
Color = onColorSK.WithAlpha(60),
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Stroke,
|
Style = SKPaintStyle.Stroke,
|
||||||
StrokeWidth = 3f
|
StrokeWidth = 3f
|
||||||
};
|
};
|
||||||
var focusRect = new SKRoundRect(trackRect.Rect, TrackHeight / 2f);
|
var focusRect = new SKRoundRect(trackRect.Rect, trackHeight / 2f);
|
||||||
focusRect.Inflate(3f, 3f);
|
focusRect.Inflate(3f, 3f);
|
||||||
canvas.DrawRoundRect(focusRect, focusPaint);
|
canvas.DrawRoundRect(focusRect, focusPaint);
|
||||||
}
|
}
|
||||||
@@ -297,6 +379,9 @@ public class SkiaSwitch : SkiaView
|
|||||||
|
|
||||||
private static SKColor InterpolateColor(SKColor from, SKColor to, float t)
|
private static SKColor InterpolateColor(SKColor from, SKColor to, float t)
|
||||||
{
|
{
|
||||||
|
// Clamp t to [0, 1]
|
||||||
|
t = Math.Max(0f, Math.Min(1f, t));
|
||||||
|
|
||||||
return new SKColor(
|
return new SKColor(
|
||||||
(byte)(from.Red + (to.Red - from.Red) * t),
|
(byte)(from.Red + (to.Red - from.Red) * t),
|
||||||
(byte)(from.Green + (to.Green - from.Green) * t),
|
(byte)(from.Green + (to.Green - from.Green) * t),
|
||||||
@@ -304,6 +389,10 @@ public class SkiaSwitch : SkiaView
|
|||||||
(byte)(from.Alpha + (to.Alpha - from.Alpha) * t));
|
(byte)(from.Alpha + (to.Alpha - from.Alpha) * t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Pointer Events
|
||||||
|
|
||||||
public override void OnPointerPressed(PointerEventArgs e)
|
public override void OnPointerPressed(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (IsEnabled)
|
if (IsEnabled)
|
||||||
@@ -315,8 +404,13 @@ public class SkiaSwitch : SkiaView
|
|||||||
|
|
||||||
public override void OnPointerReleased(PointerEventArgs e)
|
public override void OnPointerReleased(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
|
// No action needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Keyboard Events
|
||||||
|
|
||||||
public override void OnKeyDown(KeyEventArgs e)
|
public override void OnKeyDown(KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (IsEnabled && (e.Key == Key.Space || e.Key == Key.Enter))
|
if (IsEnabled && (e.Key == Key.Space || e.Key == Key.Enter))
|
||||||
@@ -326,14 +420,35 @@ public class SkiaSwitch : SkiaView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Lifecycle
|
||||||
|
|
||||||
protected override void OnEnabledChanged()
|
protected override void OnEnabledChanged()
|
||||||
{
|
{
|
||||||
base.OnEnabledChanged();
|
base.OnEnabledChanged();
|
||||||
SkiaVisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled");
|
SkiaVisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
StopAnimation();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Layout
|
||||||
|
|
||||||
protected override SKSize MeasureOverride(SKSize availableSize)
|
protected override SKSize MeasureOverride(SKSize availableSize)
|
||||||
{
|
{
|
||||||
return new SKSize(TrackWidth + 8f, TrackHeight + 8f);
|
var trackWidth = (float)TrackWidth;
|
||||||
|
var trackHeight = (float)TrackHeight;
|
||||||
|
return new SKSize(trackWidth + 8f, trackHeight + 8f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user