Preview 3: Complete control implementation with XAML data binding
Major milestone adding full control functionality: Controls Enhanced: - Entry/Editor: Full keyboard input, cursor navigation, selection, clipboard - CollectionView: Data binding, selection highlighting, scrolling - CheckBox/Switch/Slider: Interactive state management - Picker/DatePicker/TimePicker: Dropdown selection with popup overlays - ProgressBar/ActivityIndicator: Animated progress display - Button: Press/release visual states - Border/Frame: Rounded corners, stroke styling - Label: Text wrapping, alignment, decorations - Grid/StackLayout: Margin and padding support Features Added: - DisplayAlert dialogs with button actions - NavigationPage with toolbar and back navigation - Shell with flyout menu navigation - XAML value converters for data binding - Margin support in all layout containers - Popup overlay system for pickers New Samples: - TodoApp: Full CRUD task manager with NavigationPage - ShellDemo: Comprehensive control showcase Removed: - ControlGallery (replaced by ShellDemo) - LinuxDemo (replaced by TodoApp) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,66 +10,136 @@ namespace Microsoft.Maui.Platform;
|
||||
/// </summary>
|
||||
public class SkiaStepper : SkiaView
|
||||
{
|
||||
private double _value;
|
||||
private double _minimum;
|
||||
private double _maximum = 100;
|
||||
private double _increment = 1;
|
||||
private bool _isMinusPressed;
|
||||
private bool _isPlusPressed;
|
||||
#region BindableProperties
|
||||
|
||||
// Styling
|
||||
public SKColor ButtonBackgroundColor { get; set; } = new SKColor(0xE0, 0xE0, 0xE0);
|
||||
public SKColor ButtonPressedColor { get; set; } = new SKColor(0xBD, 0xBD, 0xBD);
|
||||
public SKColor ButtonDisabledColor { get; set; } = new SKColor(0xF5, 0xF5, 0xF5);
|
||||
public SKColor BorderColor { get; set; } = new SKColor(0xBD, 0xBD, 0xBD);
|
||||
public SKColor SymbolColor { get; set; } = SKColors.Black;
|
||||
public SKColor SymbolDisabledColor { get; set; } = new SKColor(0xBD, 0xBD, 0xBD);
|
||||
public float CornerRadius { get; set; } = 4;
|
||||
public float ButtonWidth { get; set; } = 40;
|
||||
public static readonly BindableProperty ValueProperty =
|
||||
BindableProperty.Create(nameof(Value), typeof(double), typeof(SkiaStepper), 0.0, BindingMode.TwoWay,
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnValuePropertyChanged((double)o, (double)n));
|
||||
|
||||
public static readonly BindableProperty MinimumProperty =
|
||||
BindableProperty.Create(nameof(Minimum), typeof(double), typeof(SkiaStepper), 0.0,
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnRangeChanged());
|
||||
|
||||
public static readonly BindableProperty MaximumProperty =
|
||||
BindableProperty.Create(nameof(Maximum), typeof(double), typeof(SkiaStepper), 100.0,
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).OnRangeChanged());
|
||||
|
||||
public static readonly BindableProperty IncrementProperty =
|
||||
BindableProperty.Create(nameof(Increment), typeof(double), typeof(SkiaStepper), 1.0);
|
||||
|
||||
public static readonly BindableProperty ButtonBackgroundColorProperty =
|
||||
BindableProperty.Create(nameof(ButtonBackgroundColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xE0, 0xE0, 0xE0),
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||
|
||||
public static readonly BindableProperty ButtonPressedColorProperty =
|
||||
BindableProperty.Create(nameof(ButtonPressedColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xBD, 0xBD, 0xBD),
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||
|
||||
public static readonly BindableProperty ButtonDisabledColorProperty =
|
||||
BindableProperty.Create(nameof(ButtonDisabledColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xF5, 0xF5, 0xF5),
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||
|
||||
public static readonly BindableProperty BorderColorProperty =
|
||||
BindableProperty.Create(nameof(BorderColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xBD, 0xBD, 0xBD),
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||
|
||||
public static readonly BindableProperty SymbolColorProperty =
|
||||
BindableProperty.Create(nameof(SymbolColor), typeof(SKColor), typeof(SkiaStepper), SKColors.Black,
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||
|
||||
public static readonly BindableProperty SymbolDisabledColorProperty =
|
||||
BindableProperty.Create(nameof(SymbolDisabledColor), typeof(SKColor), typeof(SkiaStepper), new SKColor(0xBD, 0xBD, 0xBD),
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||
|
||||
public static readonly BindableProperty CornerRadiusProperty =
|
||||
BindableProperty.Create(nameof(CornerRadius), typeof(float), typeof(SkiaStepper), 4f,
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).Invalidate());
|
||||
|
||||
public static readonly BindableProperty ButtonWidthProperty =
|
||||
BindableProperty.Create(nameof(ButtonWidth), typeof(float), typeof(SkiaStepper), 40f,
|
||||
propertyChanged: (b, o, n) => ((SkiaStepper)b).InvalidateMeasure());
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public double Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
var clamped = Math.Clamp(value, _minimum, _maximum);
|
||||
if (_value != clamped)
|
||||
{
|
||||
_value = clamped;
|
||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
get => (double)GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, Math.Clamp(value, Minimum, Maximum));
|
||||
}
|
||||
|
||||
public double Minimum
|
||||
{
|
||||
get => _minimum;
|
||||
set
|
||||
{
|
||||
_minimum = value;
|
||||
if (_value < _minimum) Value = _minimum;
|
||||
Invalidate();
|
||||
}
|
||||
get => (double)GetValue(MinimumProperty);
|
||||
set => SetValue(MinimumProperty, value);
|
||||
}
|
||||
|
||||
public double Maximum
|
||||
{
|
||||
get => _maximum;
|
||||
set
|
||||
{
|
||||
_maximum = value;
|
||||
if (_value > _maximum) Value = _maximum;
|
||||
Invalidate();
|
||||
}
|
||||
get => (double)GetValue(MaximumProperty);
|
||||
set => SetValue(MaximumProperty, value);
|
||||
}
|
||||
|
||||
public double Increment
|
||||
{
|
||||
get => _increment;
|
||||
set { _increment = Math.Max(0.001, value); Invalidate(); }
|
||||
get => (double)GetValue(IncrementProperty);
|
||||
set => SetValue(IncrementProperty, Math.Max(0.001, value));
|
||||
}
|
||||
|
||||
public SKColor ButtonBackgroundColor
|
||||
{
|
||||
get => (SKColor)GetValue(ButtonBackgroundColorProperty);
|
||||
set => SetValue(ButtonBackgroundColorProperty, value);
|
||||
}
|
||||
|
||||
public SKColor ButtonPressedColor
|
||||
{
|
||||
get => (SKColor)GetValue(ButtonPressedColorProperty);
|
||||
set => SetValue(ButtonPressedColorProperty, value);
|
||||
}
|
||||
|
||||
public SKColor ButtonDisabledColor
|
||||
{
|
||||
get => (SKColor)GetValue(ButtonDisabledColorProperty);
|
||||
set => SetValue(ButtonDisabledColorProperty, value);
|
||||
}
|
||||
|
||||
public SKColor BorderColor
|
||||
{
|
||||
get => (SKColor)GetValue(BorderColorProperty);
|
||||
set => SetValue(BorderColorProperty, value);
|
||||
}
|
||||
|
||||
public SKColor SymbolColor
|
||||
{
|
||||
get => (SKColor)GetValue(SymbolColorProperty);
|
||||
set => SetValue(SymbolColorProperty, value);
|
||||
}
|
||||
|
||||
public SKColor SymbolDisabledColor
|
||||
{
|
||||
get => (SKColor)GetValue(SymbolDisabledColorProperty);
|
||||
set => SetValue(SymbolDisabledColorProperty, value);
|
||||
}
|
||||
|
||||
public float CornerRadius
|
||||
{
|
||||
get => (float)GetValue(CornerRadiusProperty);
|
||||
set => SetValue(CornerRadiusProperty, value);
|
||||
}
|
||||
|
||||
public float ButtonWidth
|
||||
{
|
||||
get => (float)GetValue(ButtonWidthProperty);
|
||||
set => SetValue(ButtonWidthProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _isMinusPressed;
|
||||
private bool _isPlusPressed;
|
||||
|
||||
public event EventHandler? ValueChanged;
|
||||
|
||||
public SkiaStepper()
|
||||
@@ -77,19 +147,30 @@ public class SkiaStepper : SkiaView
|
||||
IsFocusable = true;
|
||||
}
|
||||
|
||||
private void OnValuePropertyChanged(double oldValue, double newValue)
|
||||
{
|
||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void OnRangeChanged()
|
||||
{
|
||||
var clamped = Math.Clamp(Value, Minimum, Maximum);
|
||||
if (Value != clamped)
|
||||
{
|
||||
Value = clamped;
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
var buttonHeight = bounds.Height;
|
||||
var minusRect = new SKRect(bounds.Left, bounds.Top, bounds.Left + ButtonWidth, bounds.Bottom);
|
||||
var plusRect = new SKRect(bounds.Right - ButtonWidth, bounds.Top, bounds.Right, bounds.Bottom);
|
||||
|
||||
// Draw minus button
|
||||
DrawButton(canvas, minusRect, "-", _isMinusPressed, !CanDecrement());
|
||||
|
||||
// Draw plus button
|
||||
DrawButton(canvas, plusRect, "+", _isPlusPressed, !CanIncrement());
|
||||
|
||||
// Draw border
|
||||
using var borderPaint = new SKPaint
|
||||
{
|
||||
Color = BorderColor,
|
||||
@@ -98,29 +179,23 @@ public class SkiaStepper : SkiaView
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
// Overall border with rounded corners
|
||||
var totalRect = new SKRect(bounds.Left, bounds.Top, bounds.Right, bounds.Bottom);
|
||||
canvas.DrawRoundRect(new SKRoundRect(totalRect, CornerRadius), borderPaint);
|
||||
|
||||
// Center divider
|
||||
var centerX = bounds.MidX;
|
||||
canvas.DrawLine(centerX, bounds.Top, centerX, bounds.Bottom, borderPaint);
|
||||
}
|
||||
|
||||
private void DrawButton(SKCanvas canvas, SKRect rect, string symbol, bool isPressed, bool isDisabled)
|
||||
{
|
||||
// Draw background
|
||||
using var bgPaint = new SKPaint
|
||||
{
|
||||
Color = isDisabled ? ButtonDisabledColor : (isPressed ? ButtonPressedColor : ButtonBackgroundColor),
|
||||
Style = SKPaintStyle.Fill,
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
// Draw button background (clipped by overall border)
|
||||
canvas.DrawRect(rect, bgPaint);
|
||||
|
||||
// Draw symbol
|
||||
using var font = new SKFont(SKTypeface.Default, 20);
|
||||
using var textPaint = new SKPaint(font)
|
||||
{
|
||||
@@ -133,23 +208,22 @@ public class SkiaStepper : SkiaView
|
||||
canvas.DrawText(symbol, rect.MidX - textBounds.MidX, rect.MidY - textBounds.MidY, textPaint);
|
||||
}
|
||||
|
||||
private bool CanIncrement() => IsEnabled && _value < _maximum;
|
||||
private bool CanDecrement() => IsEnabled && _value > _minimum;
|
||||
private bool CanIncrement() => IsEnabled && Value < Maximum;
|
||||
private bool CanDecrement() => IsEnabled && Value > Minimum;
|
||||
|
||||
public override void OnPointerPressed(PointerEventArgs e)
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
var x = e.X;
|
||||
if (x < ButtonWidth)
|
||||
if (e.X < ButtonWidth)
|
||||
{
|
||||
_isMinusPressed = true;
|
||||
if (CanDecrement()) Value -= _increment;
|
||||
if (CanDecrement()) Value -= Increment;
|
||||
}
|
||||
else if (x > Bounds.Width - ButtonWidth)
|
||||
else if (e.X > Bounds.Width - ButtonWidth)
|
||||
{
|
||||
_isPlusPressed = true;
|
||||
if (CanIncrement()) Value += _increment;
|
||||
if (CanIncrement()) Value += Increment;
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
@@ -169,12 +243,12 @@ public class SkiaStepper : SkiaView
|
||||
{
|
||||
case Key.Up:
|
||||
case Key.Right:
|
||||
if (CanIncrement()) Value += _increment;
|
||||
if (CanIncrement()) Value += Increment;
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.Down:
|
||||
case Key.Left:
|
||||
if (CanDecrement()) Value -= _increment;
|
||||
if (CanDecrement()) Value -= Increment;
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user