Slider completed
This commit is contained in:
@@ -68,7 +68,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
|||||||
base.DisconnectHandler(platformView);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValueChanged(object? sender, SliderValueChangedEventArgs e)
|
private void OnValueChanged(object? sender, ValueChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null) return;
|
if (VirtualView is null || PlatformView is null) return;
|
||||||
|
|
||||||
@@ -112,18 +112,16 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
// MinimumTrackColor maps to ActiveTrackColor (the filled portion)
|
|
||||||
if (slider.MinimumTrackColor is not null)
|
if (slider.MinimumTrackColor is not null)
|
||||||
handler.PlatformView.ActiveTrackColor = slider.MinimumTrackColor.ToSKColor();
|
handler.PlatformView.MinimumTrackColor = slider.MinimumTrackColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapMaximumTrackColor(SliderHandler handler, ISlider slider)
|
public static void MapMaximumTrackColor(SliderHandler handler, ISlider slider)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
// MaximumTrackColor maps to TrackColor (the unfilled portion)
|
|
||||||
if (slider.MaximumTrackColor is not null)
|
if (slider.MaximumTrackColor is not null)
|
||||||
handler.PlatformView.TrackColor = slider.MaximumTrackColor.ToSKColor();
|
handler.PlatformView.MaximumTrackColor = slider.MaximumTrackColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapThumbColor(SliderHandler handler, ISlider slider)
|
public static void MapThumbColor(SliderHandler handler, ISlider slider)
|
||||||
@@ -131,7 +129,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (slider.ThumbColor is not null)
|
if (slider.ThumbColor is not null)
|
||||||
handler.PlatformView.ThumbColor = slider.ThumbColor.ToSKColor();
|
handler.PlatformView.ThumbColor = slider.ThumbColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(SliderHandler handler, ISlider slider)
|
public static void MapBackground(SliderHandler handler, ISlider slider)
|
||||||
|
|||||||
@@ -3,20 +3,36 @@
|
|||||||
|
|
||||||
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 slider control with full XAML styling support.
|
/// Skia-rendered slider control with full MAUI compliance.
|
||||||
|
/// Implements ISlider interface requirements:
|
||||||
|
/// - Minimum, Maximum, Value properties
|
||||||
|
/// - MinimumTrackColor, MaximumTrackColor, ThumbColor
|
||||||
|
/// - ValueChanged, DragStarted, DragCompleted events
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SkiaSlider : SkiaView
|
public class SkiaSlider : 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 Minimum.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty MinimumProperty =
|
public static readonly BindableProperty MinimumProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(Minimum),
|
nameof(Minimum),
|
||||||
@@ -26,9 +42,6 @@ public class SkiaSlider : SkiaView
|
|||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for Maximum.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty MaximumProperty =
|
public static readonly BindableProperty MaximumProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(Maximum),
|
nameof(Maximum),
|
||||||
@@ -38,87 +51,66 @@ public class SkiaSlider : SkiaView
|
|||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bindable property for Value.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty ValueProperty =
|
public static readonly BindableProperty ValueProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(Value),
|
nameof(Value),
|
||||||
typeof(double),
|
typeof(double),
|
||||||
typeof(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
0.0,
|
0.0,
|
||||||
BindingMode.OneWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnValuePropertyChanged((double)o, (double)n));
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnValuePropertyChanged((double)o, (double)n));
|
||||||
|
|
||||||
/// <summary>
|
public static readonly BindableProperty MinimumTrackColorProperty =
|
||||||
/// Bindable property for TrackColor.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty TrackColorProperty =
|
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(TrackColor),
|
nameof(MinimumTrackColor),
|
||||||
typeof(SKColor),
|
typeof(Color),
|
||||||
typeof(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
new SKColor(0xE0, 0xE0, 0xE0),
|
Color.FromRgb(0x21, 0x96, 0xF3), // Material Blue - active track
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
public static readonly BindableProperty MaximumTrackColorProperty =
|
||||||
/// Bindable property for ActiveTrackColor.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BindableProperty ActiveTrackColorProperty =
|
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(ActiveTrackColor),
|
nameof(MaximumTrackColor),
|
||||||
typeof(SKColor),
|
typeof(Color),
|
||||||
typeof(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
new SKColor(0x21, 0x96, 0xF3),
|
Color.FromRgb(0xE0, 0xE0, 0xE0), // Gray - inactive track
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)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(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
new SKColor(0x21, 0x96, 0xF3),
|
Color.FromRgb(0x21, 0x96, 0xF3), // Material Blue
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)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(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
new SKColor(0xBD, 0xBD, 0xBD),
|
Color.FromRgb(0xBD, 0xBD, 0xBD),
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
||||||
|
|
||||||
/// <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(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
4f,
|
4.0,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
||||||
|
|
||||||
/// <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(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
10f,
|
10.0,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).InvalidateMeasure());
|
||||||
|
|
||||||
@@ -154,67 +146,74 @@ public class SkiaSlider : SkiaView
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the track color.
|
/// Gets or sets the color of the track from minimum to current value.
|
||||||
|
/// This is the "active" or "filled" portion of the track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SKColor TrackColor
|
public Color MinimumTrackColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(TrackColorProperty);
|
get => (Color)GetValue(MinimumTrackColorProperty);
|
||||||
set => SetValue(TrackColorProperty, value);
|
set => SetValue(MinimumTrackColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the active track color.
|
/// Gets or sets the color of the track from current value to maximum.
|
||||||
|
/// This is the "inactive" or "unfilled" portion of the track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SKColor ActiveTrackColor
|
public Color MaximumTrackColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(ActiveTrackColorProperty);
|
get => (Color)GetValue(MaximumTrackColorProperty);
|
||||||
set => SetValue(ActiveTrackColorProperty, value);
|
set => SetValue(MaximumTrackColorProperty, 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 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 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>
|
||||||
|
/// Gets whether the slider is currently being dragged.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDragging { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private bool _isDragging;
|
#region Events
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event raised when the value changes.
|
/// Event raised when the value changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<SliderValueChangedEventArgs>? ValueChanged;
|
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event raised when drag starts.
|
/// Event raised when drag starts.
|
||||||
@@ -226,16 +225,24 @@ public class SkiaSlider : SkiaView
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler? DragCompleted;
|
public event EventHandler? DragCompleted;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
public SkiaSlider()
|
public SkiaSlider()
|
||||||
{
|
{
|
||||||
IsFocusable = true;
|
IsFocusable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
|
||||||
private void OnRangeChanged()
|
private void OnRangeChanged()
|
||||||
{
|
{
|
||||||
// Clamp value to new range
|
// Clamp value to new range
|
||||||
var clamped = Math.Clamp(Value, Minimum, Maximum);
|
var clamped = Math.Clamp(Value, Minimum, Maximum);
|
||||||
if (Value != clamped)
|
if (Math.Abs(Value - clamped) > double.Epsilon)
|
||||||
{
|
{
|
||||||
Value = clamped;
|
Value = clamped;
|
||||||
}
|
}
|
||||||
@@ -244,49 +251,74 @@ public class SkiaSlider : SkiaView
|
|||||||
|
|
||||||
private void OnValuePropertyChanged(double oldValue, double newValue)
|
private void OnValuePropertyChanged(double oldValue, double newValue)
|
||||||
{
|
{
|
||||||
ValueChanged?.Invoke(this, new SliderValueChangedEventArgs(newValue));
|
ValueChanged?.Invoke(this, new ValueChangedEventArgs(oldValue, newValue));
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Rendering
|
||||||
|
|
||||||
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
||||||
{
|
{
|
||||||
|
var trackHeight = (float)TrackHeight;
|
||||||
|
var thumbRadius = (float)ThumbRadius;
|
||||||
|
|
||||||
var trackY = bounds.MidY;
|
var trackY = bounds.MidY;
|
||||||
var trackLeft = bounds.Left + ThumbRadius;
|
var trackLeft = bounds.Left + thumbRadius;
|
||||||
var trackRight = bounds.Right - ThumbRadius;
|
var trackRight = bounds.Right - thumbRadius;
|
||||||
var trackWidth = trackRight - trackLeft;
|
var trackWidth = trackRight - trackLeft;
|
||||||
|
|
||||||
var percentage = Maximum > Minimum ? (Value - Minimum) / (Maximum - Minimum) : 0;
|
var percentage = Maximum > Minimum ? (Value - Minimum) / (Maximum - Minimum) : 0;
|
||||||
var thumbX = trackLeft + (float)(percentage * trackWidth);
|
var thumbX = trackLeft + (float)(percentage * trackWidth);
|
||||||
|
|
||||||
// Draw inactive track
|
// Get colors
|
||||||
|
var minTrackColorSK = ToSKColor(MinimumTrackColor);
|
||||||
|
var maxTrackColorSK = ToSKColor(MaximumTrackColor);
|
||||||
|
var thumbColorSK = ToSKColor(ThumbColor);
|
||||||
|
var disabledColorSK = ToSKColor(DisabledColor);
|
||||||
|
|
||||||
|
// Draw inactive (maximum) track
|
||||||
using var inactiveTrackPaint = new SKPaint
|
using var inactiveTrackPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = IsEnabled ? TrackColor : DisabledColor,
|
Color = IsEnabled ? maxTrackColorSK : disabledColorSK,
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill
|
Style = SKPaintStyle.Fill
|
||||||
};
|
};
|
||||||
|
|
||||||
var inactiveRect = new SKRoundRect(
|
var inactiveRect = new SKRoundRect(
|
||||||
new SKRect(trackLeft, trackY - TrackHeight / 2, trackRight, trackY + TrackHeight / 2),
|
new SKRect(trackLeft, trackY - trackHeight / 2, trackRight, trackY + trackHeight / 2),
|
||||||
TrackHeight / 2);
|
trackHeight / 2);
|
||||||
canvas.DrawRoundRect(inactiveRect, inactiveTrackPaint);
|
canvas.DrawRoundRect(inactiveRect, inactiveTrackPaint);
|
||||||
|
|
||||||
// Draw active track
|
// Draw active (minimum) track
|
||||||
if (percentage > 0)
|
if (percentage > 0)
|
||||||
{
|
{
|
||||||
using var activeTrackPaint = new SKPaint
|
using var activeTrackPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = IsEnabled ? ActiveTrackColor : DisabledColor,
|
Color = IsEnabled ? minTrackColorSK : disabledColorSK,
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill
|
Style = SKPaintStyle.Fill
|
||||||
};
|
};
|
||||||
|
|
||||||
var activeRect = new SKRoundRect(
|
var activeRect = new SKRoundRect(
|
||||||
new SKRect(trackLeft, trackY - TrackHeight / 2, thumbX, trackY + TrackHeight / 2),
|
new SKRect(trackLeft, trackY - trackHeight / 2, thumbX, trackY + trackHeight / 2),
|
||||||
TrackHeight / 2);
|
trackHeight / 2);
|
||||||
canvas.DrawRoundRect(activeRect, activeTrackPaint);
|
canvas.DrawRoundRect(activeRect, activeTrackPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw focus ring behind thumb
|
||||||
|
if (IsFocused)
|
||||||
|
{
|
||||||
|
using var focusPaint = new SKPaint
|
||||||
|
{
|
||||||
|
Color = thumbColorSK.WithAlpha(60),
|
||||||
|
IsAntialias = true,
|
||||||
|
Style = SKPaintStyle.Fill
|
||||||
|
};
|
||||||
|
canvas.DrawCircle(thumbX, trackY, thumbRadius + 8, focusPaint);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw thumb shadow
|
// Draw thumb shadow
|
||||||
if (IsEnabled)
|
if (IsEnabled)
|
||||||
{
|
{
|
||||||
@@ -296,83 +328,99 @@ public class SkiaSlider : SkiaView
|
|||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 3)
|
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 3)
|
||||||
};
|
};
|
||||||
canvas.DrawCircle(thumbX + 1, trackY + 2, ThumbRadius, shadowPaint);
|
canvas.DrawCircle(thumbX + 1, trackY + 2, thumbRadius, shadowPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw thumb
|
// Draw thumb
|
||||||
using var thumbPaint = new SKPaint
|
using var thumbPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = IsEnabled ? ThumbColor : DisabledColor,
|
Color = IsEnabled ? thumbColorSK : disabledColorSK,
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill
|
Style = SKPaintStyle.Fill
|
||||||
};
|
};
|
||||||
canvas.DrawCircle(thumbX, trackY, ThumbRadius, thumbPaint);
|
canvas.DrawCircle(thumbX, trackY, thumbRadius, thumbPaint);
|
||||||
|
|
||||||
// Draw focus ring
|
// Draw pressed state (larger thumb when dragging)
|
||||||
if (IsFocused)
|
if (IsDragging)
|
||||||
{
|
{
|
||||||
using var focusPaint = new SKPaint
|
using var pressedPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = ThumbColor.WithAlpha(60),
|
Color = thumbColorSK.WithAlpha(40),
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill
|
Style = SKPaintStyle.Fill
|
||||||
};
|
};
|
||||||
canvas.DrawCircle(thumbX, trackY, ThumbRadius + 8, focusPaint);
|
canvas.DrawCircle(thumbX, trackY, thumbRadius + 4, pressedPaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Pointer Events
|
||||||
|
|
||||||
public override void OnPointerPressed(PointerEventArgs e)
|
public override void OnPointerPressed(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (!IsEnabled) return;
|
if (!IsEnabled) return;
|
||||||
|
|
||||||
_isDragging = true;
|
IsDragging = true;
|
||||||
UpdateValueFromPosition(e.X);
|
UpdateValueFromPosition(e.X);
|
||||||
DragStarted?.Invoke(this, EventArgs.Empty);
|
DragStarted?.Invoke(this, EventArgs.Empty);
|
||||||
SkiaVisualStateManager.GoToState(this, "Pressed");
|
SkiaVisualStateManager.GoToState(this, SkiaVisualStateManager.CommonStates.Pressed);
|
||||||
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnPointerMoved(PointerEventArgs e)
|
public override void OnPointerMoved(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (!IsEnabled || !_isDragging) return;
|
if (!IsEnabled || !IsDragging) return;
|
||||||
UpdateValueFromPosition(e.X);
|
UpdateValueFromPosition(e.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnPointerReleased(PointerEventArgs e)
|
public override void OnPointerReleased(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (_isDragging)
|
if (IsDragging)
|
||||||
{
|
{
|
||||||
_isDragging = false;
|
IsDragging = false;
|
||||||
DragCompleted?.Invoke(this, EventArgs.Empty);
|
DragCompleted?.Invoke(this, EventArgs.Empty);
|
||||||
SkiaVisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled");
|
SkiaVisualStateManager.GoToState(this, IsEnabled
|
||||||
|
? SkiaVisualStateManager.CommonStates.Normal
|
||||||
|
: SkiaVisualStateManager.CommonStates.Disabled);
|
||||||
|
Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateValueFromPosition(float x)
|
private void UpdateValueFromPosition(float x)
|
||||||
{
|
{
|
||||||
var trackLeft = Bounds.Left + ThumbRadius;
|
var thumbRadius = (float)ThumbRadius;
|
||||||
var trackRight = Bounds.Right - ThumbRadius;
|
var trackLeft = Bounds.Left + thumbRadius;
|
||||||
|
var trackRight = Bounds.Right - thumbRadius;
|
||||||
var trackWidth = trackRight - trackLeft;
|
var trackWidth = trackRight - trackLeft;
|
||||||
|
|
||||||
|
if (trackWidth <= 0) return;
|
||||||
|
|
||||||
var percentage = Math.Clamp((x - trackLeft) / trackWidth, 0, 1);
|
var percentage = Math.Clamp((x - trackLeft) / trackWidth, 0, 1);
|
||||||
Value = Minimum + percentage * (Maximum - Minimum);
|
Value = Minimum + percentage * (Maximum - Minimum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Keyboard Events
|
||||||
|
|
||||||
public override void OnKeyDown(KeyEventArgs e)
|
public override void OnKeyDown(KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (!IsEnabled) return;
|
if (!IsEnabled) return;
|
||||||
|
|
||||||
var step = (Maximum - Minimum) / 100; // 1% steps
|
var step = (Maximum - Minimum) / 100; // 1% steps
|
||||||
|
var largeStep = step * 10; // 10% for arrow keys
|
||||||
|
|
||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
{
|
{
|
||||||
case Key.Left:
|
case Key.Left:
|
||||||
case Key.Down:
|
case Key.Down:
|
||||||
Value -= step * 10;
|
Value -= largeStep;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
break;
|
break;
|
||||||
case Key.Right:
|
case Key.Right:
|
||||||
case Key.Up:
|
case Key.Up:
|
||||||
Value += step * 10;
|
Value += largeStep;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
break;
|
break;
|
||||||
case Key.Home:
|
case Key.Home:
|
||||||
@@ -383,26 +431,38 @@ public class SkiaSlider : SkiaView
|
|||||||
Value = Maximum;
|
Value = Maximum;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
break;
|
break;
|
||||||
|
case Key.PageDown:
|
||||||
|
Value -= (Maximum - Minimum) * 0.1; // 10%
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
case Key.PageUp:
|
||||||
|
Value += (Maximum - Minimum) * 0.1; // 10%
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
? SkiaVisualStateManager.CommonStates.Normal
|
||||||
|
: SkiaVisualStateManager.CommonStates.Disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Layout
|
||||||
|
|
||||||
protected override SKSize MeasureOverride(SKSize availableSize)
|
protected override SKSize MeasureOverride(SKSize availableSize)
|
||||||
{
|
{
|
||||||
return new SKSize(200, ThumbRadius * 2 + 16);
|
var thumbRadius = (float)ThumbRadius;
|
||||||
}
|
return new SKSize(200, thumbRadius * 2 + 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
#endregion
|
||||||
/// Event args for slider value changed events.
|
|
||||||
/// </summary>
|
|
||||||
public class SliderValueChangedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public double NewValue { get; }
|
|
||||||
public SliderValueChangedEventArgs(double newValue) => NewValue = newValue;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user