maui-linux/Views/SkiaButton.cs

247 lines
7.6 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using SkiaSharp;
using Microsoft.Maui.Platform.Linux.Rendering;
namespace Microsoft.Maui.Platform;
/// <summary>
/// Skia-rendered button control.
/// </summary>
public class SkiaButton : SkiaView
{
public string Text { get; set; } = "";
public SKColor TextColor { get; set; } = SKColors.White;
public new SKColor BackgroundColor { get; set; } = new SKColor(0x21, 0x96, 0xF3); // Material Blue
public SKColor PressedBackgroundColor { get; set; } = new SKColor(0x19, 0x76, 0xD2);
public SKColor DisabledBackgroundColor { get; set; } = new SKColor(0xBD, 0xBD, 0xBD);
public SKColor HoveredBackgroundColor { get; set; } = new SKColor(0x42, 0xA5, 0xF5);
public SKColor BorderColor { get; set; } = SKColors.Transparent;
public string FontFamily { get; set; } = "Sans";
public float FontSize { get; set; } = 14;
public bool IsBold { get; set; }
public bool IsItalic { get; set; }
public float CharacterSpacing { get; set; }
public float CornerRadius { get; set; } = 4;
public float BorderWidth { get; set; } = 0;
public SKRect Padding { get; set; } = new SKRect(16, 8, 16, 8);
public bool IsPressed { get; private set; }
public bool IsHovered { get; private set; }
private bool _focusFromKeyboard;
public event EventHandler? Clicked;
public event EventHandler? Pressed;
public event EventHandler? Released;
public SkiaButton()
{
IsFocusable = true;
}
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
{
// Determine background color based on state
var bgColor = !IsEnabled ? DisabledBackgroundColor
: IsPressed ? PressedBackgroundColor
: IsHovered ? HoveredBackgroundColor
: BackgroundColor;
// Draw shadow (for elevation effect)
if (IsEnabled && !IsPressed)
{
DrawShadow(canvas, bounds);
}
// Draw background with rounded corners
using var bgPaint = new SKPaint
{
Color = bgColor,
IsAntialias = true,
Style = SKPaintStyle.Fill
};
var rect = new SKRoundRect(bounds, CornerRadius);
canvas.DrawRoundRect(rect, bgPaint);
// Draw border
if (BorderWidth > 0 && BorderColor != SKColors.Transparent)
{
using var borderPaint = new SKPaint
{
Color = BorderColor,
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = BorderWidth
};
canvas.DrawRoundRect(rect, borderPaint);
}
// Draw focus ring only for keyboard focus
if (IsFocused && _focusFromKeyboard)
{
using var focusPaint = new SKPaint
{
Color = new SKColor(0x21, 0x96, 0xF3, 0x80),
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = 2
};
var focusRect = new SKRoundRect(bounds, CornerRadius + 2);
focusRect.Inflate(2, 2);
canvas.DrawRoundRect(focusRect, focusPaint);
}
// Draw text
if (!string.IsNullOrEmpty(Text))
{
var fontStyle = new SKFontStyle(
IsBold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal,
SKFontStyleWidth.Normal,
IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright);
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(FontFamily, fontStyle)
?? SKTypeface.Default;
using var font = new SKFont(typeface, FontSize);
using var paint = new SKPaint(font)
{
Color = IsEnabled ? TextColor : TextColor.WithAlpha(128),
IsAntialias = true
};
// Measure text
var textBounds = new SKRect();
paint.MeasureText(Text, ref textBounds);
// Center text
var x = bounds.MidX - textBounds.MidX;
var y = bounds.MidY - textBounds.MidY;
canvas.DrawText(Text, x, y, paint);
}
}
private void DrawShadow(SKCanvas canvas, SKRect bounds)
{
using var shadowPaint = new SKPaint
{
Color = new SKColor(0, 0, 0, 50),
IsAntialias = true,
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 4)
};
var shadowRect = new SKRect(
bounds.Left + 2,
bounds.Top + 4,
bounds.Right + 2,
bounds.Bottom + 4);
var roundRect = new SKRoundRect(shadowRect, CornerRadius);
canvas.DrawRoundRect(roundRect, shadowPaint);
}
public override void OnPointerEntered(PointerEventArgs e)
{
if (!IsEnabled) return;
IsHovered = true;
Invalidate();
}
public override void OnPointerExited(PointerEventArgs e)
{
IsHovered = false;
if (IsPressed)
{
IsPressed = false;
}
Invalidate();
}
public override void OnPointerPressed(PointerEventArgs e)
{
if (!IsEnabled) return;
IsPressed = true;
_focusFromKeyboard = false;
Invalidate();
Pressed?.Invoke(this, EventArgs.Empty);
}
public override void OnPointerReleased(PointerEventArgs e)
{
if (!IsEnabled) return;
var wasPressed = IsPressed;
IsPressed = false;
Invalidate();
Released?.Invoke(this, EventArgs.Empty);
// Fire click if released within bounds
if (wasPressed && Bounds.Contains(new SKPoint(e.X, e.Y)))
{
Clicked?.Invoke(this, EventArgs.Empty);
}
}
public override void OnKeyDown(KeyEventArgs e)
{
if (!IsEnabled) return;
// Activate on Enter or Space
if (e.Key == Key.Enter || e.Key == Key.Space)
{
IsPressed = true;
_focusFromKeyboard = true;
Invalidate();
Pressed?.Invoke(this, EventArgs.Empty);
e.Handled = true;
}
}
public override void OnKeyUp(KeyEventArgs e)
{
if (!IsEnabled) return;
if (e.Key == Key.Enter || e.Key == Key.Space)
{
if (IsPressed)
{
IsPressed = false;
Invalidate();
Released?.Invoke(this, EventArgs.Empty);
Clicked?.Invoke(this, EventArgs.Empty);
}
e.Handled = true;
}
}
protected override SKSize MeasureOverride(SKSize availableSize)
{
if (string.IsNullOrEmpty(Text))
{
return new SKSize(
Padding.Left + Padding.Right + 40, // Minimum width
Padding.Top + Padding.Bottom + FontSize);
}
var fontStyle = new SKFontStyle(
IsBold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal,
SKFontStyleWidth.Normal,
IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright);
var typeface = SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(FontFamily, fontStyle)
?? SKTypeface.Default;
using var font = new SKFont(typeface, FontSize);
using var paint = new SKPaint(font);
var textBounds = new SKRect();
paint.MeasureText(Text, ref textBounds);
return new SKSize(
textBounds.Width + Padding.Left + Padding.Right,
textBounds.Height + Padding.Top + Padding.Bottom);
}
}