Stepper completed
This commit is contained in:
@@ -5,12 +5,12 @@ using Microsoft.Maui.Handlers;
|
|||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handler for Stepper on Linux using Skia rendering.
|
/// Handler for Stepper on Linux using Skia rendering.
|
||||||
|
/// Maps IStepper interface to SkiaStepper platform view.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||||
{
|
{
|
||||||
@@ -52,12 +52,21 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
|||||||
// Apply dark theme colors if needed
|
// Apply dark theme colors if needed
|
||||||
if (Application.Current?.UserAppTheme == AppTheme.Dark)
|
if (Application.Current?.UserAppTheme == AppTheme.Dark)
|
||||||
{
|
{
|
||||||
platformView.ButtonBackgroundColor = new SKColor(66, 66, 66);
|
platformView.ButtonBackgroundColor = Color.FromRgb(66, 66, 66);
|
||||||
platformView.ButtonPressedColor = new SKColor(97, 97, 97);
|
platformView.ButtonPressedColor = Color.FromRgb(97, 97, 97);
|
||||||
platformView.ButtonDisabledColor = new SKColor(48, 48, 48);
|
platformView.ButtonDisabledColor = Color.FromRgb(48, 48, 48);
|
||||||
platformView.SymbolColor = new SKColor(224, 224, 224);
|
platformView.SymbolColor = Color.FromRgb(224, 224, 224);
|
||||||
platformView.SymbolDisabledColor = new SKColor(97, 97, 97);
|
platformView.SymbolDisabledColor = Color.FromRgb(97, 97, 97);
|
||||||
platformView.BorderColor = new SKColor(97, 97, 97);
|
platformView.BorderColor = Color.FromRgb(97, 97, 97);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync properties
|
||||||
|
if (VirtualView != null)
|
||||||
|
{
|
||||||
|
MapValue(this, VirtualView);
|
||||||
|
MapMinimum(this, VirtualView);
|
||||||
|
MapMaximum(this, VirtualView);
|
||||||
|
MapIsEnabled(this, VirtualView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,15 +76,21 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
|||||||
base.DisconnectHandler(platformView);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValueChanged(object? sender, EventArgs e)
|
private void OnValueChanged(object? sender, ValueChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null) return;
|
if (VirtualView is null || PlatformView is null) return;
|
||||||
VirtualView.Value = PlatformView.Value;
|
|
||||||
|
if (Math.Abs(VirtualView.Value - e.NewValue) > 0.0001)
|
||||||
|
{
|
||||||
|
VirtualView.Value = e.NewValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapValue(StepperHandler handler, IStepper stepper)
|
public static void MapValue(StepperHandler handler, IStepper stepper)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
|
if (Math.Abs(handler.PlatformView.Value - stepper.Value) > 0.0001)
|
||||||
handler.PlatformView.Value = stepper.Value;
|
handler.PlatformView.Value = stepper.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,5 +130,6 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.IsEnabled = stepper.IsEnabled;
|
handler.PlatformView.IsEnabled = stepper.IsEnabled;
|
||||||
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,205 +1,404 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform;
|
namespace Microsoft.Maui.Platform;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skia-rendered stepper control with increment/decrement buttons.
|
/// Skia-rendered stepper control with increment/decrement buttons.
|
||||||
|
/// Implements IStepper interface requirements:
|
||||||
|
/// - Minimum, Maximum, Value, Increment properties
|
||||||
|
/// - ValueChanged event with old/new values
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SkiaStepper : SkiaView
|
public class SkiaStepper : 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
|
||||||
|
|
||||||
public static readonly BindableProperty ValueProperty =
|
public static readonly BindableProperty ValueProperty =
|
||||||
BindableProperty.Create(nameof(Value), typeof(double), typeof(SkiaStepper), 0.0, BindingMode.OneWay,
|
BindableProperty.Create(
|
||||||
|
nameof(Value),
|
||||||
|
typeof(double),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
0.0,
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnValuePropertyChanged((double)o, (double)n));
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnValuePropertyChanged((double)o, (double)n));
|
||||||
|
|
||||||
public static readonly BindableProperty MinimumProperty =
|
public static readonly BindableProperty MinimumProperty =
|
||||||
BindableProperty.Create(nameof(Minimum), typeof(double), typeof(SkiaStepper), 0.0, BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(Minimum),
|
||||||
|
typeof(double),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
0.0,
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnRangeChanged());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnRangeChanged());
|
||||||
|
|
||||||
public static readonly BindableProperty MaximumProperty =
|
public static readonly BindableProperty MaximumProperty =
|
||||||
BindableProperty.Create(nameof(Maximum), typeof(double), typeof(SkiaStepper), 100.0, BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(Maximum),
|
||||||
|
typeof(double),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
100.0,
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnRangeChanged());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnRangeChanged());
|
||||||
|
|
||||||
public static readonly BindableProperty IncrementProperty =
|
public static readonly BindableProperty IncrementProperty =
|
||||||
BindableProperty.Create(nameof(Increment), typeof(double), typeof(SkiaStepper), 1.0, BindingMode.TwoWay);
|
BindableProperty.Create(
|
||||||
|
nameof(Increment),
|
||||||
|
typeof(double),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
1.0,
|
||||||
|
BindingMode.TwoWay);
|
||||||
|
|
||||||
public static readonly BindableProperty ButtonBackgroundColorProperty =
|
public static readonly BindableProperty ButtonBackgroundColorProperty =
|
||||||
BindableProperty.Create(nameof(ButtonBackgroundColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xE0, 0xE0, 0xE0), BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(ButtonBackgroundColor),
|
||||||
|
typeof(Color),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
Color.FromRgb(0xE0, 0xE0, 0xE0),
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||||
|
|
||||||
public static readonly BindableProperty ButtonPressedColorProperty =
|
public static readonly BindableProperty ButtonPressedColorProperty =
|
||||||
BindableProperty.Create(nameof(ButtonPressedColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xBD, 0xBD, 0xBD), BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(ButtonPressedColor),
|
||||||
|
typeof(Color),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
Color.FromRgb(0xBD, 0xBD, 0xBD),
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||||
|
|
||||||
public static readonly BindableProperty ButtonDisabledColorProperty =
|
public static readonly BindableProperty ButtonDisabledColorProperty =
|
||||||
BindableProperty.Create(nameof(ButtonDisabledColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xF5, 0xF5, 0xF5), BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(ButtonDisabledColor),
|
||||||
|
typeof(Color),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
Color.FromRgb(0xF5, 0xF5, 0xF5),
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||||
|
|
||||||
public static readonly BindableProperty BorderColorProperty =
|
public static readonly BindableProperty BorderColorProperty =
|
||||||
BindableProperty.Create(nameof(BorderColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xBD, 0xBD, 0xBD), BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(BorderColor),
|
||||||
|
typeof(Color),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
Color.FromRgb(0xBD, 0xBD, 0xBD),
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||||
|
|
||||||
public static readonly BindableProperty SymbolColorProperty =
|
public static readonly BindableProperty SymbolColorProperty =
|
||||||
BindableProperty.Create(nameof(SymbolColor), typeof(SKColor), typeof(SkiaStepper), SKColors.Black, BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(SymbolColor),
|
||||||
|
typeof(Color),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
Colors.Black,
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||||
|
|
||||||
public static readonly BindableProperty SymbolDisabledColorProperty =
|
public static readonly BindableProperty SymbolDisabledColorProperty =
|
||||||
BindableProperty.Create(nameof(SymbolDisabledColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xBD, 0xBD, 0xBD), BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(SymbolDisabledColor),
|
||||||
|
typeof(Color),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
Color.FromRgb(0xBD, 0xBD, 0xBD),
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||||
|
|
||||||
public static readonly BindableProperty CornerRadiusProperty =
|
public static readonly BindableProperty CornerRadiusProperty =
|
||||||
BindableProperty.Create(nameof(CornerRadius), typeof(float), typeof(SkiaStepper), 4f, BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(CornerRadius),
|
||||||
|
typeof(double),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
4.0,
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||||
|
|
||||||
public static readonly BindableProperty ButtonWidthProperty =
|
public static readonly BindableProperty ButtonWidthProperty =
|
||||||
BindableProperty.Create(nameof(ButtonWidth), typeof(float), typeof(SkiaStepper), 40f, BindingMode.TwoWay,
|
BindableProperty.Create(
|
||||||
|
nameof(ButtonWidth),
|
||||||
|
typeof(double),
|
||||||
|
typeof(SkiaStepper),
|
||||||
|
40.0,
|
||||||
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaStepper)b).InvalidateMeasure());
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current value.
|
||||||
|
/// </summary>
|
||||||
public double Value
|
public double Value
|
||||||
{
|
{
|
||||||
get => (double)GetValue(ValueProperty);
|
get => (double)GetValue(ValueProperty);
|
||||||
set => SetValue(ValueProperty, Math.Clamp(value, Minimum, Maximum));
|
set => SetValue(ValueProperty, Math.Clamp(value, Minimum, Maximum));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum value.
|
||||||
|
/// </summary>
|
||||||
public double Minimum
|
public double Minimum
|
||||||
{
|
{
|
||||||
get => (double)GetValue(MinimumProperty);
|
get => (double)GetValue(MinimumProperty);
|
||||||
set => SetValue(MinimumProperty, value);
|
set => SetValue(MinimumProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum value.
|
||||||
|
/// </summary>
|
||||||
public double Maximum
|
public double Maximum
|
||||||
{
|
{
|
||||||
get => (double)GetValue(MaximumProperty);
|
get => (double)GetValue(MaximumProperty);
|
||||||
set => SetValue(MaximumProperty, value);
|
set => SetValue(MaximumProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the increment amount.
|
||||||
|
/// </summary>
|
||||||
public double Increment
|
public double Increment
|
||||||
{
|
{
|
||||||
get => (double)GetValue(IncrementProperty);
|
get => (double)GetValue(IncrementProperty);
|
||||||
set => SetValue(IncrementProperty, Math.Max(0.001, value));
|
set => SetValue(IncrementProperty, Math.Max(0.001, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SKColor ButtonBackgroundColor
|
/// <summary>
|
||||||
|
/// Gets or sets the button background color.
|
||||||
|
/// </summary>
|
||||||
|
public Color ButtonBackgroundColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(ButtonBackgroundColorProperty);
|
get => (Color)GetValue(ButtonBackgroundColorProperty);
|
||||||
set => SetValue(ButtonBackgroundColorProperty, value);
|
set => SetValue(ButtonBackgroundColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SKColor ButtonPressedColor
|
/// <summary>
|
||||||
|
/// Gets or sets the button pressed color.
|
||||||
|
/// </summary>
|
||||||
|
public Color ButtonPressedColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(ButtonPressedColorProperty);
|
get => (Color)GetValue(ButtonPressedColorProperty);
|
||||||
set => SetValue(ButtonPressedColorProperty, value);
|
set => SetValue(ButtonPressedColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SKColor ButtonDisabledColor
|
/// <summary>
|
||||||
|
/// Gets or sets the button disabled color.
|
||||||
|
/// </summary>
|
||||||
|
public Color ButtonDisabledColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(ButtonDisabledColorProperty);
|
get => (Color)GetValue(ButtonDisabledColorProperty);
|
||||||
set => SetValue(ButtonDisabledColorProperty, value);
|
set => SetValue(ButtonDisabledColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SKColor BorderColor
|
/// <summary>
|
||||||
|
/// Gets or sets the border color.
|
||||||
|
/// </summary>
|
||||||
|
public Color BorderColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(BorderColorProperty);
|
get => (Color)GetValue(BorderColorProperty);
|
||||||
set => SetValue(BorderColorProperty, value);
|
set => SetValue(BorderColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SKColor SymbolColor
|
/// <summary>
|
||||||
|
/// Gets or sets the symbol color.
|
||||||
|
/// </summary>
|
||||||
|
public Color SymbolColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(SymbolColorProperty);
|
get => (Color)GetValue(SymbolColorProperty);
|
||||||
set => SetValue(SymbolColorProperty, value);
|
set => SetValue(SymbolColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SKColor SymbolDisabledColor
|
/// <summary>
|
||||||
|
/// Gets or sets the symbol disabled color.
|
||||||
|
/// </summary>
|
||||||
|
public Color SymbolDisabledColor
|
||||||
{
|
{
|
||||||
get => (SKColor)GetValue(SymbolDisabledColorProperty);
|
get => (Color)GetValue(SymbolDisabledColorProperty);
|
||||||
set => SetValue(SymbolDisabledColorProperty, value);
|
set => SetValue(SymbolDisabledColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float CornerRadius
|
/// <summary>
|
||||||
|
/// Gets or sets the corner radius.
|
||||||
|
/// </summary>
|
||||||
|
public double CornerRadius
|
||||||
{
|
{
|
||||||
get => (float)GetValue(CornerRadiusProperty);
|
get => (double)GetValue(CornerRadiusProperty);
|
||||||
set => SetValue(CornerRadiusProperty, value);
|
set => SetValue(CornerRadiusProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float ButtonWidth
|
/// <summary>
|
||||||
|
/// Gets or sets the button width.
|
||||||
|
/// </summary>
|
||||||
|
public double ButtonWidth
|
||||||
{
|
{
|
||||||
get => (float)GetValue(ButtonWidthProperty);
|
get => (double)GetValue(ButtonWidthProperty);
|
||||||
set => SetValue(ButtonWidthProperty, value);
|
set => SetValue(ButtonWidthProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the minus button is currently pressed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMinusPressed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the plus button is currently pressed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPlusPressed { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private bool _isMinusPressed;
|
#region Events
|
||||||
private bool _isPlusPressed;
|
|
||||||
|
|
||||||
public event EventHandler? ValueChanged;
|
/// <summary>
|
||||||
|
/// Event raised when the value changes.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
public SkiaStepper()
|
public SkiaStepper()
|
||||||
{
|
{
|
||||||
IsFocusable = true;
|
IsFocusable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
|
||||||
private void OnValuePropertyChanged(double oldValue, double newValue)
|
private void OnValuePropertyChanged(double oldValue, double newValue)
|
||||||
{
|
{
|
||||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
ValueChanged?.Invoke(this, new ValueChangedEventArgs(oldValue, newValue));
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRangeChanged()
|
private void OnRangeChanged()
|
||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Rendering
|
||||||
|
|
||||||
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
||||||
{
|
{
|
||||||
var minusRect = new SKRect(bounds.Left, bounds.Top, bounds.Left + ButtonWidth, bounds.Bottom);
|
var buttonWidth = (float)ButtonWidth;
|
||||||
var plusRect = new SKRect(bounds.Right - ButtonWidth, bounds.Top, bounds.Right, bounds.Bottom);
|
var cornerRadius = (float)CornerRadius;
|
||||||
|
|
||||||
DrawButton(canvas, minusRect, "-", _isMinusPressed, !CanDecrement());
|
var minusRect = new SKRect(bounds.Left, bounds.Top, bounds.Left + buttonWidth, bounds.Bottom);
|
||||||
DrawButton(canvas, plusRect, "+", _isPlusPressed, !CanIncrement());
|
var plusRect = new SKRect(bounds.Right - buttonWidth, bounds.Top, bounds.Right, bounds.Bottom);
|
||||||
|
|
||||||
|
// Get colors
|
||||||
|
var buttonBgColorSK = ToSKColor(ButtonBackgroundColor);
|
||||||
|
var buttonPressedColorSK = ToSKColor(ButtonPressedColor);
|
||||||
|
var buttonDisabledColorSK = ToSKColor(ButtonDisabledColor);
|
||||||
|
var borderColorSK = ToSKColor(BorderColor);
|
||||||
|
var symbolColorSK = ToSKColor(SymbolColor);
|
||||||
|
var symbolDisabledColorSK = ToSKColor(SymbolDisabledColor);
|
||||||
|
|
||||||
|
// Draw focus ring
|
||||||
|
if (IsFocused)
|
||||||
|
{
|
||||||
|
using var focusPaint = new SKPaint
|
||||||
|
{
|
||||||
|
Color = ToSKColor(Color.FromRgb(0x21, 0x96, 0xF3)).WithAlpha(60),
|
||||||
|
Style = SKPaintStyle.Stroke,
|
||||||
|
StrokeWidth = 3,
|
||||||
|
IsAntialias = true
|
||||||
|
};
|
||||||
|
var focusRect = new SKRect(bounds.Left - 2, bounds.Top - 2, bounds.Right + 2, bounds.Bottom + 2);
|
||||||
|
canvas.DrawRoundRect(new SKRoundRect(focusRect, cornerRadius + 2), focusPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawButton(canvas, minusRect, "-", IsMinusPressed, !CanDecrement(),
|
||||||
|
buttonBgColorSK, buttonPressedColorSK, buttonDisabledColorSK,
|
||||||
|
symbolColorSK, symbolDisabledColorSK, cornerRadius, true);
|
||||||
|
|
||||||
|
DrawButton(canvas, plusRect, "+", IsPlusPressed, !CanIncrement(),
|
||||||
|
buttonBgColorSK, buttonPressedColorSK, buttonDisabledColorSK,
|
||||||
|
symbolColorSK, symbolDisabledColorSK, cornerRadius, false);
|
||||||
|
|
||||||
|
// Draw border
|
||||||
using var borderPaint = new SKPaint
|
using var borderPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = BorderColor,
|
Color = borderColorSK,
|
||||||
Style = SKPaintStyle.Stroke,
|
Style = SKPaintStyle.Stroke,
|
||||||
StrokeWidth = 1,
|
StrokeWidth = 1,
|
||||||
IsAntialias = true
|
IsAntialias = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var totalRect = new SKRect(bounds.Left, bounds.Top, bounds.Right, bounds.Bottom);
|
var totalRect = new SKRect(bounds.Left, bounds.Top, bounds.Right, bounds.Bottom);
|
||||||
canvas.DrawRoundRect(new SKRoundRect(totalRect, CornerRadius), borderPaint);
|
canvas.DrawRoundRect(new SKRoundRect(totalRect, cornerRadius), borderPaint);
|
||||||
|
|
||||||
|
// Draw center divider
|
||||||
var centerX = bounds.MidX;
|
var centerX = bounds.MidX;
|
||||||
canvas.DrawLine(centerX, bounds.Top, centerX, bounds.Bottom, borderPaint);
|
canvas.DrawLine(centerX, bounds.Top, centerX, bounds.Bottom, borderPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawButton(SKCanvas canvas, SKRect rect, string symbol, bool isPressed, bool isDisabled)
|
private void DrawButton(SKCanvas canvas, SKRect rect, string symbol, bool isPressed, bool isDisabled,
|
||||||
|
SKColor bgColor, SKColor pressedColor, SKColor disabledColor,
|
||||||
|
SKColor symbolColor, SKColor symbolDisabledColor, float cornerRadius, bool isLeft)
|
||||||
{
|
{
|
||||||
|
// Draw background with rounded corners on the appropriate side
|
||||||
using var bgPaint = new SKPaint
|
using var bgPaint = new SKPaint
|
||||||
{
|
{
|
||||||
Color = isDisabled ? ButtonDisabledColor : (isPressed ? ButtonPressedColor : ButtonBackgroundColor),
|
Color = isDisabled ? disabledColor : (isPressed ? pressedColor : bgColor),
|
||||||
Style = SKPaintStyle.Fill,
|
Style = SKPaintStyle.Fill,
|
||||||
IsAntialias = true
|
IsAntialias = true
|
||||||
};
|
};
|
||||||
canvas.DrawRect(rect, bgPaint);
|
|
||||||
|
|
||||||
|
// Create path for rounded corners on one side only
|
||||||
|
using var path = new SKPath();
|
||||||
|
if (isLeft)
|
||||||
|
{
|
||||||
|
path.MoveTo(rect.Left + cornerRadius, rect.Top);
|
||||||
|
path.LineTo(rect.Right, rect.Top);
|
||||||
|
path.LineTo(rect.Right, rect.Bottom);
|
||||||
|
path.LineTo(rect.Left + cornerRadius, rect.Bottom);
|
||||||
|
path.ArcTo(new SKRect(rect.Left, rect.Bottom - cornerRadius * 2, rect.Left + cornerRadius * 2, rect.Bottom), 90, 90, false);
|
||||||
|
path.LineTo(rect.Left, rect.Top + cornerRadius);
|
||||||
|
path.ArcTo(new SKRect(rect.Left, rect.Top, rect.Left + cornerRadius * 2, rect.Top + cornerRadius * 2), 180, 90, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path.MoveTo(rect.Left, rect.Top);
|
||||||
|
path.LineTo(rect.Right - cornerRadius, rect.Top);
|
||||||
|
path.ArcTo(new SKRect(rect.Right - cornerRadius * 2, rect.Top, rect.Right, rect.Top + cornerRadius * 2), 270, 90, false);
|
||||||
|
path.LineTo(rect.Right, rect.Bottom - cornerRadius);
|
||||||
|
path.ArcTo(new SKRect(rect.Right - cornerRadius * 2, rect.Bottom - cornerRadius * 2, rect.Right, rect.Bottom), 0, 90, false);
|
||||||
|
path.LineTo(rect.Left, rect.Bottom);
|
||||||
|
}
|
||||||
|
path.Close();
|
||||||
|
canvas.DrawPath(path, bgPaint);
|
||||||
|
|
||||||
|
// Draw symbol
|
||||||
using var font = new SKFont(SKTypeface.Default, 20);
|
using var font = new SKFont(SKTypeface.Default, 20);
|
||||||
using var textPaint = new SKPaint(font)
|
using var textPaint = new SKPaint(font)
|
||||||
{
|
{
|
||||||
Color = isDisabled ? SymbolDisabledColor : SymbolColor,
|
Color = isDisabled ? symbolDisabledColor : symbolColor,
|
||||||
IsAntialias = true
|
IsAntialias = true
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -208,32 +407,71 @@ public class SkiaStepper : SkiaView
|
|||||||
canvas.DrawText(symbol, rect.MidX - textBounds.MidX, rect.MidY - textBounds.MidY, textPaint);
|
canvas.DrawText(symbol, rect.MidX - textBounds.MidX, rect.MidY - textBounds.MidY, textPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Helper Methods
|
||||||
|
|
||||||
private bool CanIncrement() => IsEnabled && Value < Maximum;
|
private bool CanIncrement() => IsEnabled && Value < Maximum;
|
||||||
private bool CanDecrement() => IsEnabled && Value > Minimum;
|
private bool CanDecrement() => IsEnabled && Value > Minimum;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Pointer Events
|
||||||
|
|
||||||
public override void OnPointerPressed(PointerEventArgs e)
|
public override void OnPointerPressed(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (!IsEnabled) return;
|
if (!IsEnabled) return;
|
||||||
|
|
||||||
if (e.X < ButtonWidth)
|
var buttonWidth = (float)ButtonWidth;
|
||||||
|
|
||||||
|
if (e.X < buttonWidth)
|
||||||
{
|
{
|
||||||
_isMinusPressed = true;
|
IsMinusPressed = true;
|
||||||
if (CanDecrement()) Value -= Increment;
|
if (CanDecrement()) Value -= Increment;
|
||||||
|
SkiaVisualStateManager.GoToState(this, SkiaVisualStateManager.CommonStates.Pressed);
|
||||||
}
|
}
|
||||||
else if (e.X > Bounds.Width - ButtonWidth)
|
else if (e.X > Bounds.Width - buttonWidth)
|
||||||
{
|
{
|
||||||
_isPlusPressed = true;
|
IsPlusPressed = true;
|
||||||
if (CanIncrement()) Value += Increment;
|
if (CanIncrement()) Value += Increment;
|
||||||
|
SkiaVisualStateManager.GoToState(this, SkiaVisualStateManager.CommonStates.Pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnPointerReleased(PointerEventArgs e)
|
public override void OnPointerReleased(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
_isMinusPressed = false;
|
if (IsMinusPressed || IsPlusPressed)
|
||||||
_isPlusPressed = false;
|
{
|
||||||
|
IsMinusPressed = false;
|
||||||
|
IsPlusPressed = false;
|
||||||
|
SkiaVisualStateManager.GoToState(this, IsEnabled
|
||||||
|
? SkiaVisualStateManager.CommonStates.Normal
|
||||||
|
: SkiaVisualStateManager.CommonStates.Disabled);
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPointerEntered(PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (IsEnabled)
|
||||||
|
{
|
||||||
|
SkiaVisualStateManager.GoToState(this, SkiaVisualStateManager.CommonStates.PointerOver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPointerExited(PointerEventArgs e)
|
||||||
|
{
|
||||||
|
SkiaVisualStateManager.GoToState(this, IsEnabled
|
||||||
|
? SkiaVisualStateManager.CommonStates.Normal
|
||||||
|
: SkiaVisualStateManager.CommonStates.Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Keyboard Events
|
||||||
|
|
||||||
public override void OnKeyDown(KeyEventArgs e)
|
public override void OnKeyDown(KeyEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -251,11 +489,46 @@ public class SkiaStepper : SkiaView
|
|||||||
if (CanDecrement()) Value -= Increment;
|
if (CanDecrement()) Value -= Increment;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
break;
|
break;
|
||||||
|
case Key.PageUp:
|
||||||
|
if (CanIncrement()) Value += Increment * 10;
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
case Key.PageDown:
|
||||||
|
if (CanDecrement()) Value -= Increment * 10;
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
case Key.Home:
|
||||||
|
Value = Minimum;
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
case Key.End:
|
||||||
|
Value = Maximum;
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Lifecycle
|
||||||
|
|
||||||
|
protected override void OnEnabledChanged()
|
||||||
|
{
|
||||||
|
base.OnEnabledChanged();
|
||||||
|
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(ButtonWidth * 2 + 1, 32);
|
var buttonWidth = (float)ButtonWidth;
|
||||||
|
return new SKSize(buttonWidth * 2 + 1, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user