Initial commit: .NET MAUI Linux Platform
Complete Linux platform implementation for .NET MAUI with:
- 35+ Skia-rendered controls (Button, Label, Entry, CarouselView, etc.)
- Platform services (Clipboard, FilePicker, Notifications, DragDrop, etc.)
- Accessibility support (AT-SPI2, High Contrast)
- HiDPI and Input Method support
- 216 unit tests
- CI/CD workflows
- Project templates
- Documentation
🤖 Generated with Claude Code
This commit is contained in:
246
Views/SkiaButton.cs
Normal file
246
Views/SkiaButton.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user