// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using SkiaSharp; namespace Microsoft.Maui.Platform; /// /// A horizontal menu bar control. /// public class SkiaMenuBar : SkiaView { private readonly List _items = new(); private int _hoveredIndex = -1; private int _openIndex = -1; private SkiaMenuFlyout? _openFlyout; /// /// Gets the menu bar items. /// public IList Items => _items; /// /// Gets or sets the background color. /// public SKColor BackgroundColor { get; set; } = new SKColor(240, 240, 240); /// /// Gets or sets the text color. /// public SKColor TextColor { get; set; } = new SKColor(33, 33, 33); /// /// Gets or sets the hover background color. /// public SKColor HoverBackgroundColor { get; set; } = new SKColor(220, 220, 220); /// /// Gets or sets the active background color. /// public SKColor ActiveBackgroundColor { get; set; } = new SKColor(200, 200, 200); /// /// Gets or sets the bar height. /// public float BarHeight { get; set; } = 28f; /// /// Gets or sets the font size. /// public float FontSize { get; set; } = 13f; /// /// Gets or sets the item padding. /// public float ItemPadding { get; set; } = 12f; protected override SKSize MeasureOverride(SKSize availableSize) { return new SKSize(availableSize.Width, BarHeight); } protected override void OnDraw(SKCanvas canvas, SKRect bounds) { canvas.Save(); // Draw background using var bgPaint = new SKPaint { Color = BackgroundColor, Style = SKPaintStyle.Fill }; canvas.DrawRect(Bounds, bgPaint); // Draw bottom border using var borderPaint = new SKPaint { Color = new SKColor(200, 200, 200), Style = SKPaintStyle.Stroke, StrokeWidth = 1 }; canvas.DrawLine(Bounds.Left, Bounds.Bottom, Bounds.Right, Bounds.Bottom, borderPaint); // Draw menu items using var textPaint = new SKPaint { Color = TextColor, TextSize = FontSize, IsAntialias = true }; float x = Bounds.Left; for (int i = 0; i < _items.Count; i++) { var item = _items[i]; var textBounds = new SKRect(); textPaint.MeasureText(item.Text, ref textBounds); float itemWidth = textBounds.Width + ItemPadding * 2; var itemBounds = new SKRect(x, Bounds.Top, x + itemWidth, Bounds.Bottom); // Draw item background if (i == _openIndex) { using var activePaint = new SKPaint { Color = ActiveBackgroundColor, Style = SKPaintStyle.Fill }; canvas.DrawRect(itemBounds, activePaint); } else if (i == _hoveredIndex) { using var hoverPaint = new SKPaint { Color = HoverBackgroundColor, Style = SKPaintStyle.Fill }; canvas.DrawRect(itemBounds, hoverPaint); } // Draw text float textX = x + ItemPadding; float textY = Bounds.MidY - textBounds.MidY; canvas.DrawText(item.Text, textX, textY, textPaint); item.Bounds = itemBounds; x += itemWidth; } // Draw open flyout _openFlyout?.Draw(canvas); canvas.Restore(); } public override SkiaView? HitTest(float x, float y) { if (!IsVisible) return null; // Check flyout first if (_openFlyout != null) { var flyoutHit = _openFlyout.HitTest(x, y); if (flyoutHit != null) return flyoutHit; } if (Bounds.Contains(x, y)) { return this; } // Close flyout if clicking outside if (_openFlyout != null) { CloseFlyout(); } return null; } public override void OnPointerMoved(PointerEventArgs e) { if (!IsEnabled) return; int newHovered = -1; for (int i = 0; i < _items.Count; i++) { if (_items[i].Bounds.Contains(e.X, e.Y)) { newHovered = i; break; } } if (newHovered != _hoveredIndex) { _hoveredIndex = newHovered; // If a menu is open and we hover another item, open that one if (_openIndex >= 0 && newHovered >= 0 && newHovered != _openIndex) { OpenFlyout(newHovered); } Invalidate(); } base.OnPointerMoved(e); } public override void OnPointerPressed(PointerEventArgs e) { if (!IsEnabled) return; // Check if clicking on flyout if (_openFlyout != null) { _openFlyout.OnPointerPressed(e); if (e.Handled) { CloseFlyout(); return; } } // Check menu bar items for (int i = 0; i < _items.Count; i++) { if (_items[i].Bounds.Contains(e.X, e.Y)) { if (_openIndex == i) { CloseFlyout(); } else { OpenFlyout(i); } e.Handled = true; return; } } // Click outside - close flyout if (_openFlyout != null) { CloseFlyout(); e.Handled = true; } base.OnPointerPressed(e); } private void OpenFlyout(int index) { if (index < 0 || index >= _items.Count) return; var item = _items[index]; _openIndex = index; _openFlyout = new SkiaMenuFlyout { Items = item.Items }; // Position below the menu item float x = item.Bounds.Left; float y = item.Bounds.Bottom; _openFlyout.Position = new SKPoint(x, y); _openFlyout.ItemClicked += OnFlyoutItemClicked; Invalidate(); } private void CloseFlyout() { if (_openFlyout != null) { _openFlyout.ItemClicked -= OnFlyoutItemClicked; _openFlyout = null; } _openIndex = -1; Invalidate(); } private void OnFlyoutItemClicked(object? sender, MenuItemClickedEventArgs e) { CloseFlyout(); } }