Files
maui-linux/Views/SkiaContextMenu.cs

218 lines
6.8 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Generic;
using SkiaSharp;
namespace Microsoft.Maui.Platform;
public class SkiaContextMenu : SkiaView
{
private readonly List<ContextMenuItem> _items;
private readonly float _x;
private readonly float _y;
private int _hoveredIndex = -1;
private SKRect[] _itemBounds = Array.Empty<SKRect>();
2026-01-24 06:13:13 +00:00
// Theme-aware colors (evaluated at draw time)
private static SKColor MenuBackground => SkiaTheme.CurrentSurfaceSK;
private static SKColor ItemHoverBackground => SkiaTheme.IsDarkMode ? SkiaTheme.DarkHoverSK : SkiaTheme.PrimarySelectionSK;
private static SKColor ItemTextColor => SkiaTheme.CurrentTextSK;
private static SKColor DisabledTextColor => SkiaTheme.TextDisabledSK;
private static SKColor SeparatorColor => SkiaTheme.IsDarkMode ? SkiaTheme.Gray600SK : SkiaTheme.Gray300SK;
private static SKColor ShadowColor => SkiaTheme.Shadow25SK;
private const float MenuPadding = 4f;
private const float ItemHeight = 32f;
private const float ItemPaddingH = 16f;
private const float SeparatorHeight = 9f;
private const float CornerRadius = 4f;
private const float MinWidth = 120f;
public SkiaContextMenu(float x, float y, List<ContextMenuItem> items, bool isDarkTheme = false)
{
_x = x;
_y = y;
_items = items;
2026-01-24 06:13:13 +00:00
// isDarkTheme parameter kept for API compatibility but ignored - we use SkiaTheme.IsDarkMode instead
IsFocusable = true;
}
public override void Draw(SKCanvas canvas)
{
float menuWidth = CalculateMenuWidth();
float menuHeight = CalculateMenuHeight();
float posX = _x;
float posY = _y;
// Adjust position to stay within bounds
canvas.GetDeviceClipBounds(out var clipBounds);
if (posX + menuWidth > clipBounds.Right)
{
posX = clipBounds.Right - menuWidth - 4f;
}
if (posY + menuHeight > clipBounds.Bottom)
{
posY = clipBounds.Bottom - menuHeight - 4f;
}
var menuRect = new SKRect(posX, posY, posX + menuWidth, posY + menuHeight);
// Draw shadow
using (var shadowPaint = new SKPaint
{
Color = ShadowColor,
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 4f)
})
{
canvas.DrawRoundRect(menuRect.Left + 2f, menuRect.Top + 2f, menuWidth, menuHeight, CornerRadius, CornerRadius, shadowPaint);
}
// Draw background
using (var bgPaint = new SKPaint
{
2026-01-24 06:13:13 +00:00
Color = MenuBackground,
IsAntialias = true
})
{
canvas.DrawRoundRect(menuRect, CornerRadius, CornerRadius, bgPaint);
}
// Draw border
using (var borderPaint = new SKPaint
{
Color = SeparatorColor,
Style = SKPaintStyle.Stroke,
StrokeWidth = 1f,
IsAntialias = true
})
{
canvas.DrawRoundRect(menuRect, CornerRadius, CornerRadius, borderPaint);
}
// Draw items
_itemBounds = new SKRect[_items.Count];
float itemY = posY + MenuPadding;
for (int i = 0; i < _items.Count; i++)
{
var item = _items[i];
if (item.IsSeparator)
{
float separatorY = itemY + SeparatorHeight / 2f;
using (var sepPaint = new SKPaint { Color = SeparatorColor, StrokeWidth = 1f })
{
canvas.DrawLine(posX + 8f, separatorY, posX + menuWidth - 8f, separatorY, sepPaint);
}
_itemBounds[i] = new SKRect(posX, itemY, posX + menuWidth, itemY + SeparatorHeight);
itemY += SeparatorHeight;
continue;
}
var itemRect = new SKRect(posX + MenuPadding, itemY, posX + menuWidth - MenuPadding, itemY + ItemHeight);
_itemBounds[i] = itemRect;
// Draw hover background
if (i == _hoveredIndex && item.IsEnabled)
{
using (var hoverPaint = new SKPaint
{
2026-01-24 06:13:13 +00:00
Color = ItemHoverBackground,
IsAntialias = true
})
{
canvas.DrawRoundRect(itemRect, CornerRadius, CornerRadius, hoverPaint);
}
}
// Draw text
using (var textPaint = new SKPaint
{
2026-01-24 06:13:13 +00:00
Color = !item.IsEnabled ? DisabledTextColor : ItemTextColor,
TextSize = 14f,
IsAntialias = true,
Typeface = SKTypeface.Default
})
{
float textY = itemRect.MidY + textPaint.TextSize / 3f;
canvas.DrawText(item.Text, itemRect.Left + ItemPaddingH, textY, textPaint);
}
itemY += ItemHeight;
}
}
private float CalculateMenuWidth()
{
float maxWidth = MinWidth;
using (var paint = new SKPaint { TextSize = 14f, Typeface = SKTypeface.Default })
{
foreach (var item in _items)
{
if (!item.IsSeparator)
{
float textWidth = paint.MeasureText(item.Text) + ItemPaddingH * 2f;
maxWidth = Math.Max(maxWidth, textWidth);
}
}
}
return maxWidth + MenuPadding * 2f;
}
private float CalculateMenuHeight()
{
float height = MenuPadding * 2f;
foreach (var item in _items)
{
height += item.IsSeparator ? SeparatorHeight : ItemHeight;
}
return height;
}
public override void OnPointerMoved(PointerEventArgs e)
{
int oldHovered = _hoveredIndex;
_hoveredIndex = -1;
for (int i = 0; i < _itemBounds.Length; i++)
{
if (_itemBounds[i].Contains(e.X, e.Y) && !_items[i].IsSeparator)
{
_hoveredIndex = i;
break;
}
}
if (oldHovered != _hoveredIndex)
{
Invalidate();
}
}
public override void OnPointerPressed(PointerEventArgs e)
{
for (int i = 0; i < _itemBounds.Length; i++)
{
if (_itemBounds[i].Contains(e.X, e.Y))
{
var item = _items[i];
if (item.IsEnabled && !item.IsSeparator && item.Action != null)
{
LinuxDialogService.HideContextMenu();
item.Action();
return;
}
}
}
LinuxDialogService.HideContextMenu();
}
public override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
LinuxDialogService.HideContextMenu();
e.Handled = true;
}
}
}