Missing bindings defaults
This commit is contained in:
@@ -29,12 +29,13 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for TextColor.
|
/// Bindable property for TextColor.
|
||||||
|
/// Default is null to match MAUI Button.TextColor (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
|
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
|
||||||
nameof(TextColor),
|
nameof(TextColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaButton),
|
typeof(SkiaButton),
|
||||||
Colors.White,
|
null,
|
||||||
propertyChanged: (b, o, n) => ((SkiaButton)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaButton)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -109,12 +110,13 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for BorderWidth.
|
/// Bindable property for BorderWidth.
|
||||||
|
/// Default is -1 to match MAUI Button.BorderWidth (unset/platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create(
|
public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create(
|
||||||
nameof(BorderWidth),
|
nameof(BorderWidth),
|
||||||
typeof(double),
|
typeof(double),
|
||||||
typeof(SkiaButton),
|
typeof(SkiaButton),
|
||||||
0.0,
|
-1.0,
|
||||||
propertyChanged: (b, o, n) => ((SkiaButton)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaButton)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -208,10 +210,11 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color of the text.
|
/// Gets or sets the color of the text.
|
||||||
|
/// Null means use platform default (white on buttons for Linux).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color TextColor
|
public Color? TextColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(TextColorProperty);
|
get => (Color?)GetValue(TextColorProperty);
|
||||||
set => SetValue(TextColorProperty, value);
|
set => SetValue(TextColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,8 +644,8 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(fontFamily, fontStyle) ?? SKTypeface.Default,
|
SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(fontFamily, fontStyle) ?? SKTypeface.Default,
|
||||||
fontSize);
|
fontSize);
|
||||||
|
|
||||||
// Prepare text color
|
// Prepare text color (null means use platform default: white for buttons)
|
||||||
var textColor = ToSKColor(TextColor);
|
var textColor = TextColor != null ? ToSKColor(TextColor) : SKColors.White;
|
||||||
if (!IsEnabled)
|
if (!IsEnabled)
|
||||||
{
|
{
|
||||||
textColor = textColor.WithAlpha(128);
|
textColor = textColor.WithAlpha(128);
|
||||||
|
|||||||
@@ -43,25 +43,27 @@ public class SkiaEditor : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for TextColor.
|
/// Bindable property for TextColor.
|
||||||
|
/// Default is null to match MAUI Editor.TextColor (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty TextColorProperty =
|
public static readonly BindableProperty TextColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(TextColor),
|
nameof(TextColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaEditor),
|
typeof(SkiaEditor),
|
||||||
Colors.Black,
|
null,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for PlaceholderColor.
|
/// Bindable property for PlaceholderColor.
|
||||||
|
/// Default is null to match MAUI Editor.PlaceholderColor (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty PlaceholderColorProperty =
|
public static readonly BindableProperty PlaceholderColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(PlaceholderColor),
|
nameof(PlaceholderColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaEditor),
|
typeof(SkiaEditor),
|
||||||
Color.FromRgb(0x80, 0x80, 0x80),
|
null,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
||||||
|
|
||||||
@@ -103,13 +105,14 @@ public class SkiaEditor : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for FontFamily.
|
/// Bindable property for FontFamily.
|
||||||
|
/// Default is empty string to match MAUI Editor.FontFamily (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty FontFamilyProperty =
|
public static readonly BindableProperty FontFamilyProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(FontFamily),
|
nameof(FontFamily),
|
||||||
typeof(string),
|
typeof(string),
|
||||||
typeof(SkiaEditor),
|
typeof(SkiaEditor),
|
||||||
"Sans",
|
string.Empty,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEditor)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaEditor)b).InvalidateMeasure());
|
||||||
|
|
||||||
@@ -307,7 +310,7 @@ public class SkiaEditor : SkiaView
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a MAUI Color to SkiaSharp SKColor.
|
/// Converts a MAUI Color to SkiaSharp SKColor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static SKColor ToSKColor(Color color)
|
private static SKColor ToSKColor(Color? color)
|
||||||
{
|
{
|
||||||
if (color == null) return SKColors.Transparent;
|
if (color == null) return SKColors.Transparent;
|
||||||
return new SKColor(
|
return new SKColor(
|
||||||
@@ -317,6 +320,30 @@ public class SkiaEditor : SkiaView
|
|||||||
(byte)(color.Alpha * 255));
|
(byte)(color.Alpha * 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the effective text color (platform default black if null).
|
||||||
|
/// </summary>
|
||||||
|
private SKColor GetEffectiveTextColor()
|
||||||
|
{
|
||||||
|
return TextColor != null ? ToSKColor(TextColor) : SKColors.Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the effective placeholder color (platform default gray if null).
|
||||||
|
/// </summary>
|
||||||
|
private SKColor GetEffectivePlaceholderColor()
|
||||||
|
{
|
||||||
|
return PlaceholderColor != null ? ToSKColor(PlaceholderColor) : new SKColor(0x80, 0x80, 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the effective font family (platform default "Sans" if empty).
|
||||||
|
/// </summary>
|
||||||
|
private string GetEffectiveFontFamily()
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(FontFamily) ? "Sans" : FontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -340,20 +367,20 @@ public class SkiaEditor : SkiaView
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the text color.
|
/// Gets or sets the text color. Null means platform default (black).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color TextColor
|
public Color? TextColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(TextColorProperty);
|
get => (Color?)GetValue(TextColorProperty);
|
||||||
set => SetValue(TextColorProperty, value);
|
set => SetValue(TextColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the placeholder color.
|
/// Gets or sets the placeholder color. Null means platform default (gray).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color PlaceholderColor
|
public Color? PlaceholderColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(PlaceholderColorProperty);
|
get => (Color?)GetValue(PlaceholderColorProperty);
|
||||||
set => SetValue(PlaceholderColorProperty, value);
|
set => SetValue(PlaceholderColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -780,14 +807,14 @@ public class SkiaEditor : SkiaView
|
|||||||
{
|
{
|
||||||
using var placeholderPaint = new SKPaint(font)
|
using var placeholderPaint = new SKPaint(font)
|
||||||
{
|
{
|
||||||
Color = ToSKColor(PlaceholderColor),
|
Color = GetEffectivePlaceholderColor(),
|
||||||
IsAntialias = true
|
IsAntialias = true
|
||||||
};
|
};
|
||||||
canvas.DrawText(Placeholder, contentRect.Left, contentRect.Top + fontSize, placeholderPaint);
|
canvas.DrawText(Placeholder, contentRect.Left, contentRect.Top + fontSize, placeholderPaint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var textColor = ToSKColor(TextColor);
|
var textColor = GetEffectiveTextColor();
|
||||||
using var textPaint = new SKPaint(font)
|
using var textPaint = new SKPaint(font)
|
||||||
{
|
{
|
||||||
Color = IsEnabled ? textColor : textColor.WithAlpha(128),
|
Color = IsEnabled ? textColor : textColor.WithAlpha(128),
|
||||||
|
|||||||
@@ -43,24 +43,26 @@ public class SkiaEntry : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for PlaceholderColor.
|
/// Bindable property for PlaceholderColor.
|
||||||
|
/// Default is null to match MAUI Entry.PlaceholderColor (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty PlaceholderColorProperty =
|
public static readonly BindableProperty PlaceholderColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(PlaceholderColor),
|
nameof(PlaceholderColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaEntry),
|
typeof(SkiaEntry),
|
||||||
Color.FromRgb(0x9E, 0x9E, 0x9E),
|
null,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEntry)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaEntry)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for TextColor.
|
/// Bindable property for TextColor.
|
||||||
|
/// Default is null to match MAUI Entry.TextColor (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty TextColorProperty =
|
public static readonly BindableProperty TextColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(TextColor),
|
nameof(TextColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaEntry),
|
typeof(SkiaEntry),
|
||||||
Colors.Black,
|
null,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEntry)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaEntry)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -120,13 +122,14 @@ public class SkiaEntry : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for FontFamily.
|
/// Bindable property for FontFamily.
|
||||||
|
/// Default is empty string to match MAUI Entry.FontFamily (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty FontFamilyProperty =
|
public static readonly BindableProperty FontFamilyProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(FontFamily),
|
nameof(FontFamily),
|
||||||
typeof(string),
|
typeof(string),
|
||||||
typeof(SkiaEntry),
|
typeof(SkiaEntry),
|
||||||
"Sans",
|
string.Empty,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEntry)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaEntry)b).InvalidateMeasure());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -229,13 +232,14 @@ public class SkiaEntry : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for VerticalTextAlignment.
|
/// Bindable property for VerticalTextAlignment.
|
||||||
|
/// Default is Start to match MAUI Entry.VerticalTextAlignment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty VerticalTextAlignmentProperty =
|
public static readonly BindableProperty VerticalTextAlignmentProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(VerticalTextAlignment),
|
nameof(VerticalTextAlignment),
|
||||||
typeof(TextAlignment),
|
typeof(TextAlignment),
|
||||||
typeof(SkiaEntry),
|
typeof(SkiaEntry),
|
||||||
TextAlignment.Center,
|
TextAlignment.Start,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEntry)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaEntry)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -365,20 +369,20 @@ public class SkiaEntry : SkiaView
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the placeholder color.
|
/// Gets or sets the placeholder color. Null means platform default (gray).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color PlaceholderColor
|
public Color? PlaceholderColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(PlaceholderColorProperty);
|
get => (Color?)GetValue(PlaceholderColorProperty);
|
||||||
set => SetValue(PlaceholderColorProperty, value);
|
set => SetValue(PlaceholderColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the text color.
|
/// Gets or sets the text color. Null means platform default (black).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color TextColor
|
public Color? TextColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(TextColorProperty);
|
get => (Color?)GetValue(TextColorProperty);
|
||||||
set => SetValue(TextColorProperty, value);
|
set => SetValue(TextColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +680,7 @@ public class SkiaEntry : SkiaView
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a MAUI Color to SkiaSharp SKColor for rendering.
|
/// Converts a MAUI Color to SkiaSharp SKColor for rendering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static SKColor ToSKColor(Color color)
|
private static SKColor ToSKColor(Color? color)
|
||||||
{
|
{
|
||||||
if (color == null) return SKColors.Transparent;
|
if (color == null) return SKColors.Transparent;
|
||||||
return new SKColor(
|
return new SKColor(
|
||||||
@@ -686,6 +690,30 @@ public class SkiaEntry : SkiaView
|
|||||||
(byte)(color.Alpha * 255));
|
(byte)(color.Alpha * 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the effective text color (platform default black if null).
|
||||||
|
/// </summary>
|
||||||
|
private SKColor GetEffectiveTextColor()
|
||||||
|
{
|
||||||
|
return TextColor != null ? ToSKColor(TextColor) : SKColors.Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the effective placeholder color (platform default gray if null).
|
||||||
|
/// </summary>
|
||||||
|
private SKColor GetEffectivePlaceholderColor()
|
||||||
|
{
|
||||||
|
return PlaceholderColor != null ? ToSKColor(PlaceholderColor) : new SKColor(0x9E, 0x9E, 0x9E);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the effective font family (platform default "Sans" if empty).
|
||||||
|
/// </summary>
|
||||||
|
private string GetEffectiveFontFamily()
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(FontFamily) ? "Sans" : FontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnTextPropertyChanged(string oldText, string newText)
|
private void OnTextPropertyChanged(string oldText, string newText)
|
||||||
{
|
{
|
||||||
_cursorPosition = Math.Min(_cursorPosition, (newText ?? "").Length);
|
_cursorPosition = Math.Min(_cursorPosition, (newText ?? "").Length);
|
||||||
@@ -742,7 +770,7 @@ public class SkiaEntry : SkiaView
|
|||||||
canvas.ClipRect(contentBounds);
|
canvas.ClipRect(contentBounds);
|
||||||
|
|
||||||
var fontStyle = GetFontStyle();
|
var fontStyle = GetFontStyle();
|
||||||
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(FontFamily, fontStyle)
|
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(GetEffectiveFontFamily(), fontStyle)
|
||||||
?? SKTypeface.Default;
|
?? SKTypeface.Default;
|
||||||
|
|
||||||
using var font = new SKFont(typeface, (float)FontSize);
|
using var font = new SKFont(typeface, (float)FontSize);
|
||||||
@@ -753,7 +781,7 @@ public class SkiaEntry : SkiaView
|
|||||||
|
|
||||||
if (hasText)
|
if (hasText)
|
||||||
{
|
{
|
||||||
paint.Color = ToSKColor(TextColor);
|
paint.Color = GetEffectiveTextColor();
|
||||||
|
|
||||||
// Measure text to cursor position for scrolling
|
// Measure text to cursor position for scrolling
|
||||||
var textToCursor = displayText.Substring(0, Math.Min(_cursorPosition, displayText.Length));
|
var textToCursor = displayText.Substring(0, Math.Min(_cursorPosition, displayText.Length));
|
||||||
@@ -798,7 +826,7 @@ public class SkiaEntry : SkiaView
|
|||||||
else if (!string.IsNullOrEmpty(Placeholder))
|
else if (!string.IsNullOrEmpty(Placeholder))
|
||||||
{
|
{
|
||||||
// Draw placeholder
|
// Draw placeholder
|
||||||
paint.Color = ToSKColor(PlaceholderColor);
|
paint.Color = GetEffectivePlaceholderColor();
|
||||||
|
|
||||||
var textBounds = new SKRect();
|
var textBounds = new SKRect();
|
||||||
paint.MeasureText(Placeholder, ref textBounds);
|
paint.MeasureText(Placeholder, ref textBounds);
|
||||||
@@ -1255,7 +1283,7 @@ public class SkiaEntry : SkiaView
|
|||||||
if (string.IsNullOrEmpty(Text)) return 0;
|
if (string.IsNullOrEmpty(Text)) return 0;
|
||||||
|
|
||||||
var fontStyle = GetFontStyle();
|
var fontStyle = GetFontStyle();
|
||||||
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(FontFamily, fontStyle)
|
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(GetEffectiveFontFamily(), fontStyle)
|
||||||
?? SKTypeface.Default;
|
?? SKTypeface.Default;
|
||||||
|
|
||||||
using var font = new SKFont(typeface, (float)FontSize);
|
using var font = new SKFont(typeface, (float)FontSize);
|
||||||
@@ -1428,7 +1456,7 @@ public class SkiaEntry : SkiaView
|
|||||||
protected override SKSize MeasureOverride(SKSize availableSize)
|
protected override SKSize MeasureOverride(SKSize availableSize)
|
||||||
{
|
{
|
||||||
var fontStyle = GetFontStyle();
|
var fontStyle = GetFontStyle();
|
||||||
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(FontFamily, fontStyle)
|
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(GetEffectiveFontFamily(), fontStyle)
|
||||||
?? SKTypeface.Default;
|
?? SKTypeface.Default;
|
||||||
|
|
||||||
using var font = new SKFont(typeface, (float)FontSize);
|
using var font = new SKFont(typeface, (float)FontSize);
|
||||||
|
|||||||
@@ -30,12 +30,13 @@ public class SkiaLabel : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for TextColor.
|
/// Bindable property for TextColor.
|
||||||
|
/// Default is null to match MAUI Label.TextColor (falls back to platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
|
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
|
||||||
nameof(TextColor),
|
nameof(TextColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaLabel),
|
typeof(SkiaLabel),
|
||||||
Colors.Black,
|
null,
|
||||||
propertyChanged: (b, o, n) => ((SkiaLabel)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaLabel)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -110,12 +111,13 @@ public class SkiaLabel : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for VerticalTextAlignment.
|
/// Bindable property for VerticalTextAlignment.
|
||||||
|
/// Default is Start to match MAUI Label.VerticalTextAlignment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty VerticalTextAlignmentProperty = BindableProperty.Create(
|
public static readonly BindableProperty VerticalTextAlignmentProperty = BindableProperty.Create(
|
||||||
nameof(VerticalTextAlignment),
|
nameof(VerticalTextAlignment),
|
||||||
typeof(TextAlignment),
|
typeof(TextAlignment),
|
||||||
typeof(SkiaLabel),
|
typeof(SkiaLabel),
|
||||||
TextAlignment.Center,
|
TextAlignment.Start,
|
||||||
propertyChanged: (b, o, n) => ((SkiaLabel)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaLabel)b).Invalidate());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -140,12 +142,13 @@ public class SkiaLabel : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for LineHeight.
|
/// Bindable property for LineHeight.
|
||||||
|
/// Default is -1 to match MAUI Label.LineHeight (platform default).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty LineHeightProperty = BindableProperty.Create(
|
public static readonly BindableProperty LineHeightProperty = BindableProperty.Create(
|
||||||
nameof(LineHeight),
|
nameof(LineHeight),
|
||||||
typeof(double),
|
typeof(double),
|
||||||
typeof(SkiaLabel),
|
typeof(SkiaLabel),
|
||||||
1.2,
|
-1.0,
|
||||||
propertyChanged: (b, o, n) => ((SkiaLabel)b).OnTextChanged());
|
propertyChanged: (b, o, n) => ((SkiaLabel)b).OnTextChanged());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -203,10 +206,11 @@ public class SkiaLabel : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the text color.
|
/// Gets or sets the text color.
|
||||||
|
/// Null means use platform default (black on Linux).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color TextColor
|
public Color? TextColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(TextColorProperty);
|
get => (Color?)GetValue(TextColorProperty);
|
||||||
set => SetValue(TextColorProperty, value);
|
set => SetValue(TextColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,7 +782,9 @@ public class SkiaLabel : SkiaView
|
|||||||
|
|
||||||
private void DrawMultiLineText(SKCanvas canvas, SKPaint paint, SKFont font, SKRect bounds, string text)
|
private void DrawMultiLineText(SKCanvas canvas, SKPaint paint, SKFont font, SKRect bounds, string text)
|
||||||
{
|
{
|
||||||
float lineHeight = (float)(FontSize * LineHeight);
|
// LineHeight -1 means platform default (use 1.0 multiplier)
|
||||||
|
double effectiveLineHeight = LineHeight < 0 ? 1.0 : LineHeight;
|
||||||
|
float lineHeight = (float)(FontSize * effectiveLineHeight);
|
||||||
float y = bounds.Top;
|
float y = bounds.Top;
|
||||||
int lineCount = 0;
|
int lineCount = 0;
|
||||||
|
|
||||||
@@ -869,7 +875,9 @@ public class SkiaLabel : SkiaView
|
|||||||
|
|
||||||
float x = bounds.Left;
|
float x = bounds.Left;
|
||||||
float y = bounds.Top;
|
float y = bounds.Top;
|
||||||
float lineHeight = (float)(FontSize * LineHeight);
|
// LineHeight -1 means platform default (use 1.0 multiplier)
|
||||||
|
double effectiveLineHeight = LineHeight < 0 ? 1.0 : LineHeight;
|
||||||
|
float lineHeight = (float)(FontSize * effectiveLineHeight);
|
||||||
float fontSize = FontSize > 0 ? (float)FontSize : 14f;
|
float fontSize = FontSize > 0 ? (float)FontSize : 14f;
|
||||||
|
|
||||||
// Calculate baseline for first line
|
// Calculate baseline for first line
|
||||||
@@ -1092,12 +1100,14 @@ public class SkiaLabel : SkiaView
|
|||||||
using var paint = new SKPaint(font);
|
using var paint = new SKPaint(font);
|
||||||
|
|
||||||
float width, height;
|
float width, height;
|
||||||
|
// LineHeight -1 means platform default (use 1.0 multiplier)
|
||||||
|
double effectiveLineHeight = LineHeight < 0 ? 1.0 : LineHeight;
|
||||||
|
|
||||||
if (FormattedText != null && FormattedText.Spans.Count > 0)
|
if (FormattedText != null && FormattedText.Spans.Count > 0)
|
||||||
{
|
{
|
||||||
// Measure formatted text
|
// Measure formatted text
|
||||||
width = 0;
|
width = 0;
|
||||||
height = (float)(fontSize * LineHeight);
|
height = (float)(fontSize * effectiveLineHeight);
|
||||||
foreach (var span in FormattedText.Spans)
|
foreach (var span in FormattedText.Spans)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(span.Text))
|
if (!string.IsNullOrEmpty(span.Text))
|
||||||
@@ -1124,7 +1134,7 @@ public class SkiaLabel : SkiaView
|
|||||||
{
|
{
|
||||||
var lines = displayText.Split('\n');
|
var lines = displayText.Split('\n');
|
||||||
int lineCount = MaxLines > 0 ? Math.Min(lines.Length, MaxLines) : lines.Length;
|
int lineCount = MaxLines > 0 ? Math.Min(lines.Length, MaxLines) : lines.Length;
|
||||||
height = (float)(lineCount * fontSize * LineHeight);
|
height = (float)(lineCount * fontSize * effectiveLineHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,12 +42,15 @@ public class SkiaSlider : SkiaView
|
|||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum property - default is 1.0 to match MAUI Slider.Maximum.
|
||||||
|
/// </summary>
|
||||||
public static readonly BindableProperty MaximumProperty =
|
public static readonly BindableProperty MaximumProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(Maximum),
|
nameof(Maximum),
|
||||||
typeof(double),
|
typeof(double),
|
||||||
typeof(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
100.0,
|
1.0, // MAUI default is 1.0, not 100.0
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).OnRangeChanged());
|
||||||
|
|
||||||
@@ -60,30 +63,39 @@ public class SkiaSlider : SkiaView
|
|||||||
BindingMode.TwoWay,
|
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>
|
||||||
|
/// MinimumTrackColor - default is null to match MAUI (platform default).
|
||||||
|
/// </summary>
|
||||||
public static readonly BindableProperty MinimumTrackColorProperty =
|
public static readonly BindableProperty MinimumTrackColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(MinimumTrackColor),
|
nameof(MinimumTrackColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
Color.FromRgb(0x21, 0x96, 0xF3), // Material Blue - active track
|
null, // MAUI default is null (platform default)
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MaximumTrackColor - default is null to match MAUI (platform default).
|
||||||
|
/// </summary>
|
||||||
public static readonly BindableProperty MaximumTrackColorProperty =
|
public static readonly BindableProperty MaximumTrackColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(MaximumTrackColor),
|
nameof(MaximumTrackColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
Color.FromRgb(0xE0, 0xE0, 0xE0), // Gray - inactive track
|
null, // MAUI default is null (platform default)
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ThumbColor - default is null to match MAUI (platform default).
|
||||||
|
/// </summary>
|
||||||
public static readonly BindableProperty ThumbColorProperty =
|
public static readonly BindableProperty ThumbColorProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(ThumbColor),
|
nameof(ThumbColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaSlider),
|
typeof(SkiaSlider),
|
||||||
Color.FromRgb(0x21, 0x96, 0xF3), // Material Blue
|
null, // MAUI default is null (platform default)
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaSlider)b).Invalidate());
|
||||||
|
|
||||||
@@ -147,33 +159,43 @@ public class SkiaSlider : SkiaView
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color of the track from minimum to current value.
|
/// Gets or sets the color of the track from minimum to current value.
|
||||||
/// This is the "active" or "filled" portion of the track.
|
/// Null means platform default (Material Blue on Linux).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color MinimumTrackColor
|
public Color? MinimumTrackColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(MinimumTrackColorProperty);
|
get => (Color?)GetValue(MinimumTrackColorProperty);
|
||||||
set => SetValue(MinimumTrackColorProperty, value);
|
set => SetValue(MinimumTrackColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color of the track from current value to maximum.
|
/// Gets or sets the color of the track from current value to maximum.
|
||||||
/// This is the "inactive" or "unfilled" portion of the track.
|
/// Null means platform default (gray on Linux).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color MaximumTrackColor
|
public Color? MaximumTrackColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(MaximumTrackColorProperty);
|
get => (Color?)GetValue(MaximumTrackColorProperty);
|
||||||
set => SetValue(MaximumTrackColorProperty, value);
|
set => SetValue(MaximumTrackColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the thumb color.
|
/// Gets or sets the thumb color.
|
||||||
|
/// Null means platform default (Material Blue on Linux).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Color ThumbColor
|
public Color? ThumbColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(ThumbColorProperty);
|
get => (Color?)GetValue(ThumbColorProperty);
|
||||||
set => SetValue(ThumbColorProperty, value);
|
set => SetValue(ThumbColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Platform defaults for colors when null
|
||||||
|
private static readonly SKColor DefaultMinimumTrackColor = new SKColor(0x21, 0x96, 0xF3); // Material Blue
|
||||||
|
private static readonly SKColor DefaultMaximumTrackColor = new SKColor(0xE0, 0xE0, 0xE0); // Gray
|
||||||
|
private static readonly SKColor DefaultThumbColor = new SKColor(0x21, 0x96, 0xF3); // Material Blue
|
||||||
|
|
||||||
|
private SKColor GetEffectiveMinimumTrackColor() => MinimumTrackColor != null ? ToSKColor(MinimumTrackColor) : DefaultMinimumTrackColor;
|
||||||
|
private SKColor GetEffectiveMaximumTrackColor() => MaximumTrackColor != null ? ToSKColor(MaximumTrackColor) : DefaultMaximumTrackColor;
|
||||||
|
private SKColor GetEffectiveThumbColor() => ThumbColor != null ? ToSKColor(ThumbColor) : DefaultThumbColor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color used when disabled.
|
/// Gets or sets the color used when disabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -272,10 +294,10 @@ public class SkiaSlider : SkiaView
|
|||||||
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);
|
||||||
|
|
||||||
// Get colors
|
// Get colors (using helper methods for platform defaults when null)
|
||||||
var minTrackColorSK = ToSKColor(MinimumTrackColor);
|
var minTrackColorSK = GetEffectiveMinimumTrackColor();
|
||||||
var maxTrackColorSK = ToSKColor(MaximumTrackColor);
|
var maxTrackColorSK = GetEffectiveMaximumTrackColor();
|
||||||
var thumbColorSK = ToSKColor(ThumbColor);
|
var thumbColorSK = GetEffectiveThumbColor();
|
||||||
var disabledColorSK = ToSKColor(DisabledColor);
|
var disabledColorSK = ToSKColor(DisabledColor);
|
||||||
|
|
||||||
// Draw inactive (maximum) track
|
// Draw inactive (maximum) track
|
||||||
|
|||||||
296
Views/SkiaStateTrigger.cs
Normal file
296
Views/SkiaStateTrigger.cs
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
|
||||||
|
namespace Microsoft.Maui.Platform;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for state triggers that automatically activate visual states.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SkiaStateTriggerBase
|
||||||
|
{
|
||||||
|
private bool _isActive;
|
||||||
|
private SkiaVisualState? _ownerState;
|
||||||
|
private SkiaView? _ownerView;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this trigger is currently active.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive
|
||||||
|
{
|
||||||
|
get => _isActive;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
if (_isActive != value)
|
||||||
|
{
|
||||||
|
_isActive = value;
|
||||||
|
OnIsActiveChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the visual state this trigger belongs to.
|
||||||
|
/// </summary>
|
||||||
|
internal SkiaVisualState? OwnerState
|
||||||
|
{
|
||||||
|
get => _ownerState;
|
||||||
|
set => _ownerState = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the view this trigger is attached to.
|
||||||
|
/// </summary>
|
||||||
|
internal SkiaView? OwnerView
|
||||||
|
{
|
||||||
|
get => _ownerView;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_ownerView = value;
|
||||||
|
OnAttached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the trigger is attached to a view.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnAttached()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when IsActive changes.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnIsActiveChanged()
|
||||||
|
{
|
||||||
|
if (_isActive && _ownerState != null && _ownerView != null)
|
||||||
|
{
|
||||||
|
SkiaVisualStateManager.GoToState(_ownerView, _ownerState.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A trigger that activates based on a boolean property.
|
||||||
|
/// Maps to MAUI StateTrigger.
|
||||||
|
/// </summary>
|
||||||
|
public class SkiaStateTrigger : SkiaStateTriggerBase
|
||||||
|
{
|
||||||
|
private bool _isActiveValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether this trigger should be active.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActiveValue
|
||||||
|
{
|
||||||
|
get => _isActiveValue;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isActiveValue = value;
|
||||||
|
IsActive = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A trigger that activates based on window size thresholds.
|
||||||
|
/// Maps to MAUI AdaptiveTrigger.
|
||||||
|
/// </summary>
|
||||||
|
public class SkiaAdaptiveTrigger : SkiaStateTriggerBase
|
||||||
|
{
|
||||||
|
private double _minWindowWidth = -1;
|
||||||
|
private double _minWindowHeight = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum window width for this trigger to activate.
|
||||||
|
/// </summary>
|
||||||
|
public double MinWindowWidth
|
||||||
|
{
|
||||||
|
get => _minWindowWidth;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_minWindowWidth = value;
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum window height for this trigger to activate.
|
||||||
|
/// </summary>
|
||||||
|
public double MinWindowHeight
|
||||||
|
{
|
||||||
|
get => _minWindowHeight;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_minWindowHeight = value;
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAttached()
|
||||||
|
{
|
||||||
|
base.OnAttached();
|
||||||
|
// Subscribe to window size changes if needed
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIsActive()
|
||||||
|
{
|
||||||
|
if (OwnerView == null)
|
||||||
|
{
|
||||||
|
IsActive = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current window size from the view's bounds
|
||||||
|
var width = OwnerView.Bounds.Width;
|
||||||
|
var height = OwnerView.Bounds.Height;
|
||||||
|
|
||||||
|
bool widthMet = _minWindowWidth < 0 || width >= _minWindowWidth;
|
||||||
|
bool heightMet = _minWindowHeight < 0 || height >= _minWindowHeight;
|
||||||
|
|
||||||
|
IsActive = widthMet && heightMet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A trigger that activates when a property equals a specific value.
|
||||||
|
/// Maps to MAUI CompareStateTrigger.
|
||||||
|
/// </summary>
|
||||||
|
public class SkiaCompareStateTrigger : SkiaStateTriggerBase
|
||||||
|
{
|
||||||
|
private object? _property;
|
||||||
|
private object? _value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the property value to compare.
|
||||||
|
/// </summary>
|
||||||
|
public object? Property
|
||||||
|
{
|
||||||
|
get => _property;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_property = value;
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value to compare against.
|
||||||
|
/// </summary>
|
||||||
|
public object? Value
|
||||||
|
{
|
||||||
|
get => _value;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIsActive()
|
||||||
|
{
|
||||||
|
if (_property == null && _value == null)
|
||||||
|
{
|
||||||
|
IsActive = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_property == null || _value == null)
|
||||||
|
{
|
||||||
|
IsActive = _property == _value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to compare values
|
||||||
|
IsActive = _property.Equals(_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A trigger that activates based on device idiom (Desktop, Phone, Tablet, etc.).
|
||||||
|
/// </summary>
|
||||||
|
public class SkiaDeviceStateTrigger : SkiaStateTriggerBase
|
||||||
|
{
|
||||||
|
private string _deviceType = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the device type to match (Desktop, Phone, Tablet, Watch, TV).
|
||||||
|
/// </summary>
|
||||||
|
public string DeviceType
|
||||||
|
{
|
||||||
|
get => _deviceType;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_deviceType = value;
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAttached()
|
||||||
|
{
|
||||||
|
base.OnAttached();
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIsActive()
|
||||||
|
{
|
||||||
|
// On Linux, we're always Desktop
|
||||||
|
IsActive = string.Equals(_deviceType, "Desktop", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A trigger that activates based on orientation (Portrait or Landscape).
|
||||||
|
/// </summary>
|
||||||
|
public class SkiaOrientationStateTrigger : SkiaStateTriggerBase
|
||||||
|
{
|
||||||
|
private SkiaDisplayOrientation _orientation = SkiaDisplayOrientation.Portrait;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the orientation to match.
|
||||||
|
/// </summary>
|
||||||
|
public SkiaDisplayOrientation Orientation
|
||||||
|
{
|
||||||
|
get => _orientation;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_orientation = value;
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAttached()
|
||||||
|
{
|
||||||
|
base.OnAttached();
|
||||||
|
UpdateIsActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIsActive()
|
||||||
|
{
|
||||||
|
if (OwnerView == null)
|
||||||
|
{
|
||||||
|
IsActive = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var width = OwnerView.Bounds.Width;
|
||||||
|
var height = OwnerView.Bounds.Height;
|
||||||
|
|
||||||
|
var currentOrientation = width > height
|
||||||
|
? SkiaDisplayOrientation.Landscape
|
||||||
|
: SkiaDisplayOrientation.Portrait;
|
||||||
|
|
||||||
|
IsActive = currentOrientation == _orientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display orientation values for state triggers.
|
||||||
|
/// </summary>
|
||||||
|
public enum SkiaDisplayOrientation
|
||||||
|
{
|
||||||
|
Portrait,
|
||||||
|
Landscape
|
||||||
|
}
|
||||||
@@ -151,24 +151,26 @@ public abstract class SkiaView : BindableObject, IDisposable
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for MinimumWidthRequest.
|
/// Bindable property for MinimumWidthRequest.
|
||||||
|
/// Default is -1 (unset) to match MAUI View.MinimumWidthRequest.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty MinimumWidthRequestProperty =
|
public static readonly BindableProperty MinimumWidthRequestProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(MinimumWidthRequest),
|
nameof(MinimumWidthRequest),
|
||||||
typeof(double),
|
typeof(double),
|
||||||
typeof(SkiaView),
|
typeof(SkiaView),
|
||||||
0.0,
|
-1.0,
|
||||||
propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bindable property for MinimumHeightRequest.
|
/// Bindable property for MinimumHeightRequest.
|
||||||
|
/// Default is -1 (unset) to match MAUI View.MinimumHeightRequest.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly BindableProperty MinimumHeightRequestProperty =
|
public static readonly BindableProperty MinimumHeightRequestProperty =
|
||||||
BindableProperty.Create(
|
BindableProperty.Create(
|
||||||
nameof(MinimumHeightRequest),
|
nameof(MinimumHeightRequest),
|
||||||
typeof(double),
|
typeof(double),
|
||||||
typeof(SkiaView),
|
typeof(SkiaView),
|
||||||
0.0,
|
-1.0,
|
||||||
propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure());
|
propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -5,9 +5,53 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Microsoft.Maui.Platform;
|
namespace Microsoft.Maui.Platform;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a visual state with setters and optional triggers.
|
||||||
|
/// Maps to MAUI VisualState.
|
||||||
|
/// </summary>
|
||||||
public class SkiaVisualState
|
public class SkiaVisualState
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of this visual state.
|
||||||
|
/// </summary>
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the setters that define property changes for this state.
|
||||||
|
/// </summary>
|
||||||
public List<SkiaVisualStateSetter> Setters { get; } = new List<SkiaVisualStateSetter>();
|
public List<SkiaVisualStateSetter> Setters { get; } = new List<SkiaVisualStateSetter>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state triggers that can automatically activate this state.
|
||||||
|
/// </summary>
|
||||||
|
public List<SkiaStateTriggerBase> StateTriggers { get; } = new List<SkiaStateTriggerBase>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the target type this state applies to.
|
||||||
|
/// </summary>
|
||||||
|
public Type? TargetType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches triggers to the specified view.
|
||||||
|
/// </summary>
|
||||||
|
internal void AttachTriggers(SkiaView view)
|
||||||
|
{
|
||||||
|
foreach (var trigger in StateTriggers)
|
||||||
|
{
|
||||||
|
trigger.OwnerState = this;
|
||||||
|
trigger.OwnerView = view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detaches triggers from the view.
|
||||||
|
/// </summary>
|
||||||
|
internal void DetachTriggers()
|
||||||
|
{
|
||||||
|
foreach (var trigger in StateTriggers)
|
||||||
|
{
|
||||||
|
trigger.OwnerState = null;
|
||||||
|
trigger.OwnerView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,34 @@ public static class SkiaVisualStateManager
|
|||||||
|
|
||||||
private static void OnVisualStateGroupsChanged(BindableObject bindable, object? oldValue, object? newValue)
|
private static void OnVisualStateGroupsChanged(BindableObject bindable, object? oldValue, object? newValue)
|
||||||
{
|
{
|
||||||
if (bindable is SkiaView view && newValue is SkiaVisualStateGroupList groups)
|
if (bindable is SkiaView view)
|
||||||
{
|
{
|
||||||
// Initialize to default state
|
// Detach old triggers
|
||||||
GoToState(view, CommonStates.Normal);
|
if (oldValue is SkiaVisualStateGroupList oldGroups)
|
||||||
|
{
|
||||||
|
foreach (var group in oldGroups)
|
||||||
|
{
|
||||||
|
foreach (var state in group.States)
|
||||||
|
{
|
||||||
|
state.DetachTriggers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach new triggers
|
||||||
|
if (newValue is SkiaVisualStateGroupList groups)
|
||||||
|
{
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
foreach (var state in group.States)
|
||||||
|
{
|
||||||
|
state.AttachTriggers(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize to default state
|
||||||
|
GoToState(view, CommonStates.Normal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,33 +5,94 @@ using Microsoft.Maui.Controls;
|
|||||||
|
|
||||||
namespace Microsoft.Maui.Platform;
|
namespace Microsoft.Maui.Platform;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a property setter within a visual state.
|
||||||
|
/// Maps to MAUI Setter class.
|
||||||
|
/// </summary>
|
||||||
public class SkiaVisualStateSetter
|
public class SkiaVisualStateSetter
|
||||||
{
|
{
|
||||||
private object? _originalValue;
|
private object? _originalValue;
|
||||||
private bool _hasOriginalValue;
|
private bool _hasOriginalValue;
|
||||||
|
private SkiaView? _targetView;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the property to set.
|
||||||
|
/// </summary>
|
||||||
public BindableProperty? Property { get; set; }
|
public BindableProperty? Property { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value to set.
|
||||||
|
/// </summary>
|
||||||
public object? Value { get; set; }
|
public object? Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the target element within a template.
|
||||||
|
/// If null, the setter applies to the root element.
|
||||||
|
/// </summary>
|
||||||
|
public string? TargetName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the setter value to the view.
|
||||||
|
/// </summary>
|
||||||
public void Apply(SkiaView view)
|
public void Apply(SkiaView view)
|
||||||
{
|
{
|
||||||
if (Property != null)
|
var target = ResolveTarget(view);
|
||||||
|
if (target == null || Property == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_hasOriginalValue)
|
||||||
{
|
{
|
||||||
if (!_hasOriginalValue)
|
_originalValue = target.GetValue(Property);
|
||||||
{
|
_hasOriginalValue = true;
|
||||||
_originalValue = view.GetValue(Property);
|
_targetView = target;
|
||||||
_hasOriginalValue = true;
|
|
||||||
}
|
|
||||||
view.SetValue(Property, Value);
|
|
||||||
}
|
}
|
||||||
|
target.SetValue(Property, Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restores the original value on the view.
|
||||||
|
/// </summary>
|
||||||
public void Unapply(SkiaView view)
|
public void Unapply(SkiaView view)
|
||||||
{
|
{
|
||||||
if (Property != null && _hasOriginalValue)
|
var target = _targetView ?? ResolveTarget(view);
|
||||||
|
if (target == null || Property == null || !_hasOriginalValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
target.SetValue(Property, _originalValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the target view based on TargetName.
|
||||||
|
/// </summary>
|
||||||
|
private SkiaView? ResolveTarget(SkiaView view)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(TargetName))
|
||||||
|
return view;
|
||||||
|
|
||||||
|
// Find named element in visual tree
|
||||||
|
return FindNamedElement(view, TargetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds a named element in the visual tree.
|
||||||
|
/// </summary>
|
||||||
|
private static SkiaView? FindNamedElement(SkiaView root, string name)
|
||||||
|
{
|
||||||
|
// Check if root has the name (using Name property if available)
|
||||||
|
if (root.Name == name)
|
||||||
|
return root;
|
||||||
|
|
||||||
|
// Search children if it's a layout
|
||||||
|
if (root is SkiaLayoutView layout)
|
||||||
{
|
{
|
||||||
view.SetValue(Property, _originalValue);
|
foreach (var child in layout.Children)
|
||||||
|
{
|
||||||
|
var found = FindNamedElement(child, name);
|
||||||
|
if (found != null)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user