Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Linq
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.CommandActions
Imports Nexamas.UI.Composition
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' Official Nexamas UI command palette surface. It filters and invokes commands through
    ''' CommandActionSystem / MASCommandRegistry and deliberately does not create a second command
    ''' catalog, global keyboard hook, popup service, command scanner, telemetry stream, or task queue.
    ''' </summary>
    Partial Public NotInheritable Class MASCommandPalette
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

        Private Shared ReadOnly _contract As MASResolvedComponentContract =
            MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASCommandPalette))

        Private NotInheritable Class PaletteRow
            Friend Sub New(index As Integer,
                           entry As MASCommandCatalogEntry,
                           enabled As Boolean,
                           boundsPx As SKRect,
                           score As Integer,
                           isRecent As Boolean)
                Me.Index = index
                Me.Entry = entry
                Me.Enabled = enabled
                Me.BoundsPx = boundsPx
                Me.Score = score
                Me.IsRecent = isRecent
            End Sub

            Friend ReadOnly Property Index As Integer
            Friend ReadOnly Property Entry As MASCommandCatalogEntry
            Friend ReadOnly Property Enabled As Boolean
            Friend ReadOnly Property Score As Integer
            Friend ReadOnly Property IsRecent As Boolean
            Friend Property BoundsPx As SKRect
        End Class

        Private ReadOnly _ownedRegistry As MASCommandRegistry = MASCommandRegistry.Create()
        Private ReadOnly _shadowPainter As New MASShadowPainter()
        Private _activeRegistry As MASCommandRegistry = _ownedRegistry
        Private ReadOnly _boundCommandIds As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
        Private ReadOnly _recentCommandIds As New List(Of String)()
        Private _lastRows As PaletteRow() = New PaletteRow() {}
        Private _lastBoundsPx As SKRect = SKRect.Empty
        Private _lastDpi As Single = 1.0F
        Private _lastLayoutPlan As MASCommandPaletteLayoutPlan = MASCommandPaletteLayoutPlan.Create(CType(Nothing, MASResponsiveLayoutProfile))
        Private _topRowIndex As Integer
        Private _title As String = "Command Palette"
        Private _placeholderText As String = "Type a command or action name"
        Private _emptyText As String = "No matching commands"
        Private _query As String = String.Empty
        Private _selectedIndex As Integer = -1
        Private _hoverIndex As Integer = -1
        Private _disposedLocal As Boolean

#End Region

#Region "Events"

        ''' <summary>
        ''' Raised after the current selected command changes.
        ''' </summary>
        Public Event SelectionChanged As EventHandler

        ''' <summary>
        ''' Raised after a command is executed through the official CommandActionSystem route.
        ''' </summary>
        Public Event CommandExecuted As EventHandler

        ''' <summary>
        ''' Raised after command rows are added, removed, or replaced.
        ''' </summary>
        Public Event CommandsChanged As EventHandler

#End Region

#Region "Constructor / Factory"

        Public Sub New()
            MyBase.New()
            RefreshSelectionAfterCommandsChanged()
        End Sub

        Public Shared Function Create(Optional title As String = Nothing) As MASCommandPalette
            Dim palette As New MASCommandPalette()
            palette._title = NormalizeText(title, "Command Palette")
            Return palette
        End Function

#End Region

#Region "Public API"

        Public Property Title As String
            Get
                Return _title
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeText(value, "Command Palette")
                If String.Equals(_title, normalized, StringComparison.Ordinal) Then Return

                _title = normalized
                RaiseTextChanged()
                RequestSizeLayoutRefreshForSizeAffectingChange("MASCommandPalette.Title")
                InvalidateVisual()
            End Set
        End Property

        Public Property PlaceholderText As String
            Get
                Return _placeholderText
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeText(value, "Type a command or action name")
                If String.Equals(_placeholderText, normalized, StringComparison.Ordinal) Then Return

                _placeholderText = normalized
                RaiseTextChanged()
                InvalidateVisual()
            End Set
        End Property

        Public Property EmptyText As String
            Get
                Return _emptyText
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeText(value, "No matching commands")
                If String.Equals(_emptyText, normalized, StringComparison.Ordinal) Then Return

                _emptyText = normalized
                RaiseTextChanged()
                InvalidateVisual()
            End Set
        End Property

        Public Property Query As String
            Get
                Return _query
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeQuery(value)
                If String.Equals(_query, normalized, StringComparison.Ordinal) Then Return

                Dim previousIndex As Integer = _selectedIndex
                _query = normalized
                _topRowIndex = 0
                _selectedIndex = ResolveFirstSelectableIndex(BuildRows(SKRect.Empty, Nothing, False))
                StartCommandPaletteSelectionMotion(previousIndex, _selectedIndex)
                RaiseTextChanged()
                RaiseSelectionChangedSafe()
                InvalidateVisual()
            End Set
        End Property

        Public ReadOnly Property CommandCount As Integer
            Get
                Return BuildEntries().Length
            End Get
        End Property

        Public ReadOnly Property ResultCount As Integer
            Get
                Return BuildRows(SKRect.Empty, Nothing, False).Length
            End Get
        End Property

        Public ReadOnly Property SelectedIndex As Integer
            Get
                Return _selectedIndex
            End Get
        End Property

        Public ReadOnly Property SelectedCommandId As String
            Get
                Dim rows As PaletteRow() = BuildRows(SKRect.Empty, Nothing, False)
                If _selectedIndex < 0 OrElse _selectedIndex >= rows.Length Then Return String.Empty
                If rows(_selectedIndex) Is Nothing OrElse rows(_selectedIndex).Entry Is Nothing Then Return String.Empty
                Return rows(_selectedIndex).Entry.CommandId
            End Get
        End Property

        Public Function AddCommand(commandId As String,
                                   text As String,
                                   execute As Action,
                                   Optional canExecute As Func(Of Boolean) = Nothing,
                                   Optional category As String = Nothing,
                                   Optional description As String = Nothing) As MASCommandPalette
            Dim id As String = MASCommandRegistry.NormalizeCommandId(commandId)
            If id.Length = 0 Then Throw New ArgumentException("A command id is required.", NameOf(commandId))
            If execute Is Nothing Then Throw New ArgumentNullException(NameOf(execute))

            Dim canExecuteRoute As Func(Of MASCommandExecutionContext, Boolean) = Nothing
            If canExecute IsNot Nothing Then
                canExecuteRoute = Function(context As MASCommandExecutionContext) canExecute.Invoke()
            End If

            _activeRegistry = _ownedRegistry
            _ownedRegistry.Register(
                id,
                NormalizeText(text, id),
                Sub(context As MASCommandExecutionContext)
                    execute.Invoke()
                End Sub,
                canExecuteRoute,
                category:=NormalizeText(category, String.Empty),
                description:=NormalizeText(description, String.Empty))
            _ownedRegistry.BindCommandPalette(Me, Name, id)

            RefreshSelectionAfterCommandsChanged()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASCommandPalette.AddCommand")
            InvalidateVisual()
            RaiseCommandsChangedSafe()
            Return Me
        End Function

        Public Function RemoveCommand(commandId As String) As MASCommandPalette
            Dim id As String = MASCommandRegistry.NormalizeCommandId(commandId)
            If id.Length = 0 Then Return Me

            If Object.ReferenceEquals(_activeRegistry, _ownedRegistry) Then
                _ownedRegistry.ReleaseRegisteredCommand(id)
            End If

            _boundCommandIds.Remove(id)
            RefreshSelectionAfterCommandsChanged()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASCommandPalette.RemoveCommand")
            InvalidateVisual()
            RaiseCommandsChangedSafe()
            Return Me
        End Function

        Public Function ClearCommands() As MASCommandPalette
            If Object.ReferenceEquals(_activeRegistry, _ownedRegistry) Then
                For Each entry As MASCommandCatalogEntry In _ownedRegistry.CreateSnapshot().Commands
                    If entry IsNot Nothing Then _ownedRegistry.ReleaseRegisteredCommand(entry.CommandId)
                Next
            End If

            _boundCommandIds.Clear()
            StopCommandPaletteSelectionMotion(resetState:=True)
            _selectedIndex = -1
            _hoverIndex = -1
            RequestSizeLayoutRefreshForSizeAffectingChange("MASCommandPalette.ClearCommands")
            InvalidateVisual()
            RaiseSelectionChangedSafe()
            RaiseCommandsChangedSafe()
            Return Me
        End Function

        Public Function ExecuteSelectedCommand() As Boolean
            Dim id As String = SelectedCommandId
            If id.Length = 0 OrElse _activeRegistry Is Nothing Then Return False

            Dim result As MASCommandInvocationResult = _activeRegistry.TryExecute(id, MASCommandBindingSurfaceKind.CommandPalette, ResolveSourceName(), Me)
            If result IsNot Nothing AndAlso result.Executed Then
                RecordRecentCommand(id)
                RaiseCommandExecutedSafe()
                InvalidateVisual()
                Return True
            End If

            Return False
        End Function

        Public Function SelectNext() As MASCommandPalette
            MoveSelection(1)
            Return Me
        End Function

        Public Function SelectPrevious() As MASCommandPalette
            MoveSelection(-1)
            Return Me
        End Function

        Public Function WithTitle(value As String) As MASCommandPalette
            Title = value
            Return Me
        End Function

        Public Function WithQuery(value As String) As MASCommandPalette
            Query = value
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASCommandPalette
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

#End Region

#Region "CommandActionSystem bridge"

        Friend Sub AttachCommandRegistryInternal(registry As MASCommandRegistry,
                                                 sourceName As String,
                                                 commandIds As IEnumerable(Of String))
            If registry Is Nothing Then Return

            _activeRegistry = registry
            _boundCommandIds.Clear()

            If commandIds IsNot Nothing Then
                For Each commandId As String In commandIds
                    Dim id As String = MASCommandRegistry.NormalizeCommandId(commandId)
                    If id.Length > 0 Then _boundCommandIds.Add(id)
                Next
            End If

            If Not String.IsNullOrWhiteSpace(sourceName) Then Name = sourceName.Trim()
            RefreshSelectionAfterCommandsChanged()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASCommandPalette.AttachCommandRegistryInternal")
            InvalidateVisual()
            RaiseCommandsChangedSafe()
        End Sub

#End Region

#Region "Layout"

        Friend Function GetPreferredLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetPreferredLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).DesiredSize
        End Function

        Friend Function GetMinLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetMinLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).MinSize
        End Function

        Friend Function MeasureIntrinsicSize(context As MASSizeContext,
                                             intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Dim safeContext As MASSizeContext = MASIntrinsicControlSizeMetrics.EnsureContext(context)
            Dim desired As New SKSize(CommandPaletteTokens.DesiredWidthDip, CommandPaletteTokens.DesiredHeightDip)
            Dim minimum As New SKSize(CommandPaletteTokens.MinWidthDip, CommandPaletteTokens.MinHeightDip)
            Dim inset As New MASLayoutInset(CommandPaletteTokens.PaddingDip,
                                            CommandPaletteTokens.PaddingDip,
                                            CommandPaletteTokens.PaddingDip,
                                            CommandPaletteTokens.PaddingDip)

            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(Single.PositiveInfinity, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=True,
                contentInset:=inset,
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)
        End Function

#End Region

#Region "Rendering"

        Protected Overrides Sub Render(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       pixelBounds As SKRect)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If pixelBounds.Width <= 1.0F OrElse pixelBounds.Height <= 1.0F Then Return

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            Dim plan As MASCommandPaletteLayoutPlan = MASCommandPaletteLayoutPlan.Create(ctx, bounds, SizeIntent)
            _lastDpi = dpi
            _lastLayoutPlan = plan
            _lastBoundsPx = bounds

            DrawSurface(canvas, ctx, bounds, plan)
            DrawHeader(canvas, ctx, bounds, plan)
            DrawSearch(canvas, ctx, bounds, plan)
            DrawRows(canvas, ctx, bounds, plan)
        End Sub

        Private Sub DrawSurface(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                bounds As SKRect,
                                plan As MASCommandPaletteLayoutPlan)
            Dim dpi As Single = plan.Dpi
            Dim radius As Single = Math.Max(plan.CornerRadiusPx,
                                            MASVisualPrimitivesPainter.ResolveSurfaceRadiusPx(_contract, bounds, dpi))

            _shadowPainter.DrawLayeredShadow(
                canvas:=canvas,
                r:=bounds,
                contract:=_contract,
                dpi:=dpi,
                surfaceTheme:=ctx.SurfaceTheme)

            Dim surfaceMaterialDrawn As Boolean = MASSurfaceMaterialComponentGateway.TryDrawPopupSurface(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=bounds,
                materialKey:=MASSurfaceMaterialIds.Auto,
                dpi:=dpi,
                cornerRadiusPx:=radius,
                surfaceStrengthLevel:=5,
                borderStrengthLevel:=4,
                frameStrength:=MASSurfaceFrameStrength.Level4)

            If surfaceMaterialDrawn Then Return

            Dim fill As SKColor = ResolvePanelColor(ctx).WithAlpha(CommandPaletteTokens.SurfaceAlpha)
            Using paint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = fill
            }
                canvas.DrawRoundRect(bounds, radius, radius, paint)
            End Using

            Using border As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = plan.StrokePx,
                .Color = ResolveAccentColor(ctx).WithAlpha(CommandPaletteTokens.BorderAlpha)
            }
                canvas.DrawRoundRect(bounds, radius, radius, border)
            End Using
        End Sub

        Private Sub DrawHeader(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               bounds As SKRect,
                               plan As MASCommandPaletteLayoutPlan)
            If plan Is Nothing OrElse Not plan.ShowHeader Then Return
            Dim dpi As Single = plan.MetricDpi
            Dim rect As SKRect = plan.ResolveHeaderRect(bounds)
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return
            Dim metaRect As SKRect = If(plan.ShowMetadata, New SKRect(rect.MidX, rect.Top, rect.Right, rect.Bottom), SKRect.Empty)
            Dim titleRect As SKRect = If(plan.ShowMetadata, New SKRect(rect.Left, rect.Top, metaRect.Left - 8.0F * dpi, rect.Bottom), rect)
            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=_title,
                bounds:=titleRect,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Title,
                color:=ResolveTitleColor(ctx).WithAlpha(CommandPaletteTokens.TitleAlpha),
                align:=TypographyTextPrimitives.TextAlign.Left,
                drawShadow:=False)
            If plan.ShowMetadata Then
                Dim metaText As String = ResultCount.ToString(CultureInfo.CurrentCulture) & " results / " & CommandCount.ToString(CultureInfo.CurrentCulture)
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, metaText, metaRect, dpi, MASTypography.MASTextStyle.Micro, ResolveMutedColor(ctx).WithAlpha(CommandPaletteTokens.MutedAlpha), TypographyTextPrimitives.TextAlign.Right, False)
            End If
        End Sub

        Private Sub DrawSearch(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               bounds As SKRect,
                               plan As MASCommandPaletteLayoutPlan)
            Dim dpi As Single = plan.MetricDpi
            Dim rect As SKRect = ResolveSearchRect(bounds, plan)
            Dim radius As Single = plan.SearchRadiusPx

            Using fill As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = ResolveInputColor(ctx).WithAlpha(CommandPaletteTokens.SearchFillAlpha)
            }
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using

            Using border As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = If(HasKeyboardFocusVisual(), plan.FocusStrokePx, plan.StrokePx),
                .Color = ResolveAccentColor(ctx).WithAlpha(If(HasKeyboardFocusVisual(), CommandPaletteTokens.FocusAlpha, CommandPaletteTokens.BorderAlpha))
            }
                canvas.DrawRoundRect(rect, radius, radius, border)
            End Using

            Dim textRect As New SKRect(rect.Left + plan.RowPaddingHorizontalPx,
                                       rect.Top,
                                       rect.Right - plan.RowPaddingHorizontalPx,
                                       rect.Bottom)
            Dim hasQuery As Boolean = _query.Length > 0
            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=If(hasQuery, _query, _placeholderText),
                bounds:=textRect,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Body,
                color:=If(hasQuery, ResolveTitleColor(ctx).WithAlpha(CommandPaletteTokens.BodyAlpha), ResolveMutedColor(ctx).WithAlpha(CommandPaletteTokens.MutedAlpha)),
                align:=TypographyTextPrimitives.TextAlign.Left,
                drawShadow:=False)
        End Sub

        Private Sub DrawRows(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             bounds As SKRect,
                             plan As MASCommandPaletteLayoutPlan)
            NormalizeViewportState(False)
            Dim rows As PaletteRow() = BuildRows(bounds, plan, True)
            _lastRows = rows

            If rows.Length = 0 Then
                DrawEmpty(canvas, ctx, bounds, plan)
                Return
            End If

            DrawCommandPaletteSelectionMotionOverlay(canvas, ctx, rows, plan)

            For i As Integer = 0 To rows.Length - 1
                DrawRow(canvas, ctx, rows(i), plan)
            Next
        End Sub

        Private Sub DrawEmpty(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              bounds As SKRect,
                              plan As MASCommandPaletteLayoutPlan)
            Dim dpi As Single = plan.MetricDpi
            Dim resultsRect As SKRect = ResolveResultsRect(bounds, plan)
            Dim centerY As Single = resultsRect.MidY - 18.0F * dpi
            Dim titleRect As New SKRect(resultsRect.Left, centerY - 16.0F * dpi, resultsRect.Right, centerY + 8.0F * dpi)
            Dim helpRect As New SKRect(resultsRect.Left, titleRect.Bottom + 4.0F * dpi, resultsRect.Right, titleRect.Bottom + 26.0F * dpi)
            Dim keysRect As New SKRect(resultsRect.Left, helpRect.Bottom + 4.0F * dpi, resultsRect.Right, helpRect.Bottom + 24.0F * dpi)
            Dim helpText As String = If(_query.Length > 0, "Try a shorter phrase, category, or command id.", "Start typing to filter commands.")
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _emptyText, titleRect, dpi, MASTypography.MASTextStyle.Body, ResolveMutedColor(ctx).WithAlpha(CommandPaletteTokens.EmptyAlpha), TypographyTextPrimitives.TextAlign.Center, False)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, helpText, helpRect, dpi, MASTypography.MASTextStyle.Small, ResolveMutedColor(ctx).WithAlpha(CommandPaletteTokens.MutedAlpha), TypographyTextPrimitives.TextAlign.Center, False)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, "↑ ↓ navigate  •  Enter execute  •  Esc clears query", keysRect, dpi, MASTypography.MASTextStyle.Micro, ResolveMutedColor(ctx).WithAlpha(CommandPaletteTokens.DisabledAlpha), TypographyTextPrimitives.TextAlign.Center, False)
        End Sub

        Private Sub DrawRow(canvas As SKCanvas,
                            ctx As MASThemeContext,
                            row As PaletteRow,
                            plan As MASCommandPaletteLayoutPlan)
            If row Is Nothing OrElse row.Entry Is Nothing Then Return
            Dim dpi As Single = plan.MetricDpi
            Dim rect As SKRect = row.BoundsPx
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return

            Dim selected As Boolean = (row.Index = _selectedIndex)
            Dim hover As Boolean = (row.Index = _hoverIndex)
            If selected AndAlso ShouldSuppressCommandPaletteSelectionFill(row.Index) Then selected = False
            If selected OrElse hover Then
                Using fill As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Fill,
                    .Color = ResolveAccentColor(ctx).WithAlpha(If(selected, CommandPaletteTokens.RowSelectedAlpha, CommandPaletteTokens.RowHoverAlpha))
                }
                    Dim radius As Single = plan.RowRadiusPx
                    canvas.DrawRoundRect(rect, radius, radius, fill)
                End Using
            End If
            If selected Then
                Using marker As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolveAccentColor(ctx).WithAlpha(CommandPaletteTokens.FocusAlpha)}
                    Dim markerRect As New SKRect(rect.Left, rect.Top + 8.0F * dpi, rect.Left + 3.0F * dpi, rect.Bottom - 8.0F * dpi)
                    canvas.DrawRoundRect(markerRect, 1.5F * dpi, 1.5F * dpi, marker)
                End Using
            End If

            Dim left As Single = rect.Left + plan.RowPaddingHorizontalPx
            Dim right As Single = rect.Right - plan.RowPaddingHorizontalPx
            Dim categoryWidth As Single = If(plan.ShowCategory, Math.Min(plan.CategoryWidthPx, rect.Width * 0.26F), 0.0F)
            Dim shortcutWidth As Single = If(plan.ShowShortcut, Math.Min(plan.ShortcutWidthPx, rect.Width * 0.18F), 0.0F)
            Dim categoryRect As New SKRect(left, rect.Top, left + categoryWidth, rect.Bottom)
            Dim shortcutRect As New SKRect(right - shortcutWidth, rect.Top, right, rect.Bottom)
            Dim textLeft As Single = If(plan.ShowCategory, categoryRect.Right + 8.0F * dpi, left)
            Dim textRight As Single = If(plan.ShowShortcut, shortcutRect.Left - 8.0F * dpi, right)
            If textRight < textLeft Then textRight = right
            Dim titleHeightPx As Single = If(plan.ShowDescription, 20.0F * dpi, rect.Height - (plan.RowPaddingVerticalPx * 2.0F))
            Dim titleRect As New SKRect(textLeft, rect.Top + plan.RowPaddingVerticalPx, textRight, rect.Top + plan.RowPaddingVerticalPx + titleHeightPx)
            Dim descRect As New SKRect(titleRect.Left, titleRect.Bottom + 2.0F * dpi, titleRect.Right, rect.Bottom - plan.RowPaddingVerticalPx)

            Dim titleColor As SKColor = If(row.Enabled, ResolveTitleColor(ctx).WithAlpha(CommandPaletteTokens.BodyAlpha), ResolveMutedColor(ctx).WithAlpha(CommandPaletteTokens.DisabledAlpha))
            Dim mutedColor As SKColor = ResolveMutedColor(ctx).WithAlpha(If(row.Enabled, CommandPaletteTokens.MutedAlpha, CommandPaletteTokens.DisabledAlpha))

            If plan.ShowCategory AndAlso categoryRect.Width > 8.0F * dpi Then
                Dim categoryText As String = If(row.Entry.Category, String.Empty)
                If categoryText.Length = 0 Then categoryText = "Command"
                Dim categoryPill As SKRect = PixelSnap.SnapRect(New SKRect(categoryRect.Left, categoryRect.MidY - 10.0F * dpi, categoryRect.Right - 4.0F * dpi, categoryRect.MidY + 10.0F * dpi), dpi)
                Using categoryFill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolveAccentColor(ctx).WithAlpha(If(row.IsRecent, CByte(38), CByte(24)))}
                    canvas.DrawRoundRect(categoryPill, 10.0F * dpi, 10.0F * dpi, categoryFill)
                End Using
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, categoryText, InsetHorizontal(categoryPill, 8.0F * dpi), dpi, MASTypography.MASTextStyle.Micro, mutedColor, TypographyTextPrimitives.TextAlign.Center, False)
            End If
            Dim titleText As String = If(row.IsRecent AndAlso _query.Length = 0, "★ " & row.Entry.Text, row.Entry.Text)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, titleText, titleRect, dpi, MASTypography.MASTextStyle.Body, titleColor, TypographyTextPrimitives.TextAlign.Left, False)
            If plan.ShowDescription AndAlso descRect.Height > 8.0F * dpi Then
                Dim description As String = If(row.Entry.Description, String.Empty)
                If description.Length = 0 Then description = row.Entry.CommandId
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, description, descRect, dpi, MASTypography.MASTextStyle.Small, mutedColor, TypographyTextPrimitives.TextAlign.Left, False)
            End If
            If plan.ShowShortcut AndAlso shortcutRect.Width > 8.0F * dpi Then
                Dim shortcutText As String = If(row.Entry.ShortcutText, String.Empty)
                If shortcutText.Length = 0 AndAlso row.Score > 0 AndAlso _query.Length > 0 Then shortcutText = "score " & row.Score.ToString(CultureInfo.InvariantCulture)
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, shortcutText, shortcutRect, dpi, MASTypography.MASTextStyle.Micro, mutedColor, TypographyTextPrimitives.TextAlign.Right, False)
            End If
        End Sub

#End Region

#Region "Input"

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return True
        End Function

        Protected Overrides Function OnMouseDown(ctx As MASThemeContext,
                                                 ptPx As SKPoint,
                                                 button As Integer) As Boolean
            If button <> 1 Then Return False
            Dim index As Integer = ResolveRowIndexAt(ptPx)
            If index >= 0 Then
                SelectIndex(index)
                Return True
            End If

            Return HitTest(ctx, ptPx)
        End Function

        Protected Overrides Function OnMouseUp(ctx As MASThemeContext,
                                               ptPx As SKPoint,
                                               button As Integer) As Boolean
            If button <> 1 Then Return False
            Dim index As Integer = ResolveRowIndexAt(ptPx)
            If index >= 0 AndAlso index = _selectedIndex Then
                ExecuteSelectedCommand()
                Return True
            End If

            Return False
        End Function

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext,
                                            ptPx As SKPoint)
            Dim index As Integer = ResolveRowIndexAt(ptPx)
            If _hoverIndex = index Then Return
            _hoverIndex = index
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            If _hoverIndex < 0 Then Return
            _hoverIndex = -1
            InvalidateVisual()
        End Sub

        Friend Overrides Function WantsPointerWheel(ctx As MASThemeContext,
                                                      ptPx As SKPoint) As Boolean
            Return Visible AndAlso Enabled AndAlso
                   ResolveResultsRect(_lastBoundsPx, _lastLayoutPlan).Contains(ptPx.X, ptPx.Y) AndAlso
                   HasScrollableRows()
        End Function

        Friend Overrides Function CanHandlePointerWheel(ctx As MASThemeContext,
                                                        delta As Integer,
                                                        ptPx As SKPoint) As Boolean
            If delta = 0 OrElse Not WantsPointerWheel(ctx, ptPx) Then Return False
            Return CanScrollRows(delta)
        End Function

        Protected Overrides Sub OnMouseWheel(ctx As MASThemeContext,
                                             delta As Integer,
                                             ptPx As SKPoint)
            If Not CanScrollRows(delta) Then Return
            If delta < 0 Then
                _topRowIndex = Math.Min(ResolveMaxTopRowIndex(), _topRowIndex + 1)
            ElseIf delta > 0 Then
                _topRowIndex = Math.Max(0, _topRowIndex - 1)
            End If
            InvalidateVisual()
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            Select Case keyCode
                Case Keys.Up
                    MoveSelection(-1)
                    Return True
                Case Keys.Down
                    MoveSelection(1)
                    Return True
                Case Keys.PageUp
                    MoveSelection(-Math.Max(1, ResolveVisibleRowCount()))
                    Return True
                Case Keys.PageDown
                    MoveSelection(Math.Max(1, ResolveVisibleRowCount()))
                    Return True
                Case Keys.Home
                    SelectIndex(0)
                    Return True
                Case Keys.End
                    SelectIndex(BuildRows(SKRect.Empty, Nothing, False).Length - 1)
                    Return True
                Case Keys.Enter
                    Return ExecuteSelectedCommand()
                Case Keys.Back
                    If _query.Length > 0 Then
                        Query = _query.Substring(0, _query.Length - 1)
                        Return True
                    End If
                Case Keys.Delete, Keys.Escape
                    If _query.Length > 0 Then
                        Query = String.Empty
                        Return True
                    End If
            End Select

            Return False
        End Function

        Protected Overrides Function OnTextInput(ctx As MASThemeContext,
                                                 text As String) As Boolean
            If String.IsNullOrEmpty(text) Then Return False
            Dim appended As Boolean = False
            For Each ch As Char In text
                If Not Char.IsControl(ch) Then
                    _query &= ch
                    appended = True
                End If
            Next

            If appended Then
                Dim previousIndex As Integer = _selectedIndex
                _query = NormalizeQuery(_query)
                _topRowIndex = 0
                _selectedIndex = ResolveFirstSelectableIndex(BuildRows(SKRect.Empty, Nothing, False))
                StartCommandPaletteSelectionMotion(previousIndex, _selectedIndex)
                RaiseTextChanged()
                RaiseSelectionChangedSafe()
                InvalidateVisual()
            End If

            Return appended
        End Function

#End Region

#Region "Helpers"

        Private Function BuildEntries() As MASCommandCatalogEntry()
            If _activeRegistry Is Nothing Then Return New MASCommandCatalogEntry() {}
            Dim snapshot As MASCommandRegistrySnapshot = _activeRegistry.CreateSnapshot()
            Dim entries As IEnumerable(Of MASCommandCatalogEntry) = snapshot.Commands.Where(Function(entry) entry IsNot Nothing)
            If _boundCommandIds.Count > 0 Then
                entries = entries.Where(Function(entry) _boundCommandIds.Contains(entry.CommandId))
            End If

            Return entries.ToArray()
        End Function

        Private Function BuildRows(boundsPx As SKRect,
                                   plan As MASCommandPaletteLayoutPlan,
                                   withBounds As Boolean) As PaletteRow()
            Dim entries As MASCommandCatalogEntry() = BuildEntries()
            Dim query As String = _query
            If query.Length > 0 Then
                entries = entries.Where(Function(entry) MatchesQuery(entry, query)).OrderByDescending(Function(entry) CalculateQueryScore(entry, query)).ThenBy(Function(entry) If(entry.Category, String.Empty)).ThenBy(Function(entry) If(entry.Text, String.Empty)).ToArray()
            Else
                entries = entries.OrderByDescending(Function(entry) ResolveRecentWeight(entry.CommandId)).ThenBy(Function(entry) If(entry.Category, String.Empty)).ThenBy(Function(entry) If(entry.Text, String.Empty)).ToArray()
            End If

            Dim limitedEntries As MASCommandCatalogEntry() = entries.Take(CommandPaletteTokens.MaxRenderedRows).ToArray()
            Dim rows As New List(Of PaletteRow)()
            Dim layoutPlan As MASCommandPaletteLayoutPlan = If(plan, _lastLayoutPlan)
            If layoutPlan Is Nothing Then layoutPlan = MASCommandPaletteLayoutPlan.Create(CType(Nothing, MASResponsiveLayoutProfile))
            Dim resultsRect As SKRect = If(withBounds, ResolveResultsRect(boundsPx, layoutPlan), SKRect.Empty)
            Dim y As Single = resultsRect.Top
            Dim rowHeight As Single = layoutPlan.RowHeightPx
            Dim rowGap As Single = layoutPlan.RowGapPx
            Dim startIndex As Integer = If(withBounds, Math.Max(0, Math.Min(_topRowIndex, Math.Max(0, limitedEntries.Length - 1))), 0)

            For index As Integer = startIndex To limitedEntries.Length - 1
                Dim entry As MASCommandCatalogEntry = limitedEntries(index)
                Dim enabled As Boolean = True
                If withBounds Then
                    enabled = If(_activeRegistry Is Nothing, False, _activeRegistry.CanExecute(entry.CommandId, MASCommandBindingSurfaceKind.CommandPalette, ResolveSourceName(), Me))
                End If
                Dim rect As SKRect = If(withBounds,
                                        New SKRect(resultsRect.Left, y, resultsRect.Right, Math.Min(y + rowHeight, resultsRect.Bottom)),
                                        SKRect.Empty)
                If Not withBounds OrElse rect.Bottom <= resultsRect.Bottom Then
                    rows.Add(New PaletteRow(index, entry, enabled, rect, CalculateQueryScore(entry, query), IsRecentCommand(entry.CommandId)))
                End If
                y += rowHeight + rowGap
                If withBounds AndAlso y > resultsRect.Bottom Then Exit For
            Next

            Return rows.ToArray()
        End Function

        Private Sub NormalizeViewportState(raiseSelectionChanged As Boolean)
            Dim rows As PaletteRow() = BuildRows(SKRect.Empty, Nothing, False)
            If rows.Length = 0 Then
                Dim changedToEmpty As Boolean = _selectedIndex <> -1
                _selectedIndex = -1
                _topRowIndex = 0
                If changedToEmpty AndAlso raiseSelectionChanged Then RaiseSelectionChangedSafe()
                Return
            End If

            Dim normalizedSelection As Integer = _selectedIndex
            If normalizedSelection < 0 Then normalizedSelection = 0
            If normalizedSelection >= rows.Length Then normalizedSelection = rows.Length - 1
            Dim selectionChanged As Boolean = normalizedSelection <> _selectedIndex
            _selectedIndex = normalizedSelection
            _topRowIndex = EnsureVisibleIndex(_selectedIndex, _topRowIndex, rows.Length, ResolveVisibleRowCount())
            If selectionChanged AndAlso raiseSelectionChanged Then RaiseSelectionChangedSafe()
        End Sub

        Private Function HasScrollableRows() As Boolean
            Return BuildRows(SKRect.Empty, Nothing, False).Length > ResolveVisibleRowCount()
        End Function

        Private Function ResolveVisibleRowCount() As Integer
            If _lastBoundsPx.IsEmpty Then Return CommandPaletteTokens.MaxRenderedRows
            Dim plan As MASCommandPaletteLayoutPlan = If(_lastLayoutPlan, MASCommandPaletteLayoutPlan.Create(CType(Nothing, MASResponsiveLayoutProfile)))
            Dim resultsRect As SKRect = ResolveResultsRect(_lastBoundsPx, plan)
            If resultsRect.Height <= 1.0F Then Return 1
            Dim rowHeight As Single = plan.RowHeightPx
            Dim rowGap As Single = plan.RowGapPx
            Return Math.Max(1, Math.Min(CommandPaletteTokens.MaxRenderedRows, CInt(Math.Floor((resultsRect.Height + rowGap) / Math.Max(1.0F, rowHeight + rowGap)))))
        End Function

        Private Function ResolveMaxTopRowIndex() As Integer
            Dim rowCount As Integer = BuildRows(SKRect.Empty, Nothing, False).Length
            Return Math.Max(0, rowCount - ResolveVisibleRowCount())
        End Function

        Private Function CanScrollRows(delta As Integer) As Boolean
            If delta = 0 OrElse Not HasScrollableRows() Then Return False
            Dim maxTop As Integer = ResolveMaxTopRowIndex()
            If delta < 0 Then Return _topRowIndex < maxTop
            If delta > 0 Then Return _topRowIndex > 0
            Return False
        End Function

        Private Shared Function EnsureVisibleIndex(index As Integer,
                                                   firstIndex As Integer,
                                                   count As Integer,
                                                   visibleCount As Integer) As Integer
            If count <= 0 OrElse visibleCount <= 0 Then Return 0
            Dim normalized As Integer = Math.Max(0, Math.Min(index, count - 1))
            Dim first As Integer = Math.Max(0, firstIndex)
            Dim maxFirst As Integer = Math.Max(0, count - visibleCount)
            If normalized < first Then first = normalized
            If normalized >= first + visibleCount Then first = normalized - visibleCount + 1
            If first > maxFirst Then first = maxFirst
            Return Math.Max(0, first)
        End Function

        Private Shared Function MatchesQuery(entry As MASCommandCatalogEntry,
                                             query As String) As Boolean
            If entry Is Nothing Then Return False
            Dim q As String = If(query, String.Empty).Trim()
            If q.Length = 0 Then Return True
            Return CalculateQueryScore(entry, q) > 0
        End Function

        Private Shared Function ContainsIgnoreCase(value As String,
                                                   query As String) As Boolean
            Return If(value, String.Empty).IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0
        End Function

        Private Shared Function CalculateQueryScore(entry As MASCommandCatalogEntry,
                                                     query As String) As Integer
            If entry Is Nothing Then Return 0
            Dim q As String = If(query, String.Empty).Trim()
            If q.Length = 0 Then Return 1000
            Dim score As Integer = 0
            score = Math.Max(score, ScoreText(entry.Text, q, 120))
            score = Math.Max(score, ScoreText(entry.CommandId, q, 95))
            score = Math.Max(score, ScoreText(entry.Category, q, 72))
            score = Math.Max(score, ScoreText(entry.Description, q, 56))
            Return score
        End Function

        Private Shared Function ScoreText(value As String,
                                          query As String,
                                          baseScore As Integer) As Integer
            Dim text As String = If(value, String.Empty).Trim()
            Dim q As String = If(query, String.Empty).Trim()
            If text.Length = 0 OrElse q.Length = 0 Then Return 0
            Dim direct As Integer = text.IndexOf(q, StringComparison.OrdinalIgnoreCase)
            If direct >= 0 Then Return Math.Max(1, baseScore - direct)

            Dim queryIndex As Integer = 0
            Dim firstMatch As Integer = -1
            For i As Integer = 0 To text.Length - 1
                If Char.ToUpperInvariant(text(i)) = Char.ToUpperInvariant(q(queryIndex)) Then
                    If firstMatch < 0 Then firstMatch = i
                    queryIndex += 1
                    If queryIndex >= q.Length Then Exit For
                End If
            Next
            If queryIndex < q.Length Then Return 0
            Return Math.Max(1, baseScore \ 2 - Math.Max(0, firstMatch))
        End Function

        Private Sub RecordRecentCommand(commandId As String)
            Dim id As String = MASCommandRegistry.NormalizeCommandId(commandId)
            If id.Length = 0 Then Return
            _recentCommandIds.RemoveAll(Function(value) String.Equals(value, id, StringComparison.OrdinalIgnoreCase))
            _recentCommandIds.Insert(0, id)
            If _recentCommandIds.Count > 8 Then _recentCommandIds.RemoveRange(8, _recentCommandIds.Count - 8)
        End Sub

        Private Function IsRecentCommand(commandId As String) As Boolean
            Return ResolveRecentWeight(commandId) > 0
        End Function

        Private Function ResolveRecentWeight(commandId As String) As Integer
            Dim id As String = MASCommandRegistry.NormalizeCommandId(commandId)
            If id.Length = 0 Then Return 0
            For i As Integer = 0 To _recentCommandIds.Count - 1
                If String.Equals(_recentCommandIds(i), id, StringComparison.OrdinalIgnoreCase) Then Return _recentCommandIds.Count - i
            Next
            Return 0
        End Function

        Private Sub RefreshSelectionAfterCommandsChanged()
            Dim rows As PaletteRow() = BuildRows(SKRect.Empty, Nothing, False)
            Dim nextIndex As Integer = ResolveFirstSelectableIndex(rows)
            Dim changed As Boolean = _selectedIndex <> nextIndex
            _selectedIndex = nextIndex
            NormalizeViewportState(False)
            If changed Then RaiseSelectionChangedSafe()
        End Sub

        Private Shared Function ResolveFirstSelectableIndex(rows As PaletteRow()) As Integer
            If rows Is Nothing OrElse rows.Length = 0 Then Return -1
            Return 0
        End Function

        Private Sub MoveSelection(delta As Integer)
            Dim rows As PaletteRow() = BuildRows(SKRect.Empty, Nothing, False)
            If rows.Length = 0 Then
                SelectIndex(-1)
                Return
            End If

            Dim nextIndex As Integer = _selectedIndex
            If nextIndex < 0 Then
                nextIndex = 0
            Else
                nextIndex += delta
            End If

            If nextIndex < 0 Then nextIndex = 0
            If nextIndex >= rows.Length Then nextIndex = rows.Length - 1
            SelectIndex(nextIndex)
        End Sub

        Private Sub SelectIndex(index As Integer)
            Dim rows As PaletteRow() = BuildRows(SKRect.Empty, Nothing, False)
            Dim normalized As Integer = index
            If rows.Length = 0 Then normalized = -1
            If normalized < -1 Then normalized = -1
            If normalized >= rows.Length Then normalized = rows.Length - 1
            If _selectedIndex = normalized Then Return

            Dim previousIndex As Integer = _selectedIndex
            _selectedIndex = normalized
            NormalizeViewportState(False)
            StartCommandPaletteSelectionMotion(previousIndex, _selectedIndex)
            InvalidateVisual()
            RaiseSelectionChangedSafe()
        End Sub

        Private Function ResolveRowIndexAt(ptPx As SKPoint) As Integer
            If _lastRows Is Nothing Then Return -1
            For i As Integer = 0 To _lastRows.Length - 1
                Dim row As PaletteRow = _lastRows(i)
                If row IsNot Nothing AndAlso row.BoundsPx.Contains(ptPx.X, ptPx.Y) Then Return row.Index
            Next
            Return -1
        End Function

        Private Shared Function ResolveSearchRect(bounds As SKRect,
                                                  plan As MASCommandPaletteLayoutPlan) As SKRect
            Dim safePlan As MASCommandPaletteLayoutPlan = If(plan, MASCommandPaletteLayoutPlan.Create(CType(Nothing, MASResponsiveLayoutProfile)))
            Return safePlan.ResolveSearchRect(bounds)
        End Function

        Private Shared Function ResolveResultsRect(bounds As SKRect,
                                                   plan As MASCommandPaletteLayoutPlan) As SKRect
            Dim safePlan As MASCommandPaletteLayoutPlan = If(plan, MASCommandPaletteLayoutPlan.Create(CType(Nothing, MASResponsiveLayoutProfile)))
            Return safePlan.ResolveResultsRect(bounds)
        End Function

        Private Function ResolveSourceName() As String
            If Not String.IsNullOrWhiteSpace(Name) Then Return Name.Trim()
            Return "MASCommandPalette"
        End Function

        Private Function HasKeyboardFocusVisual() As Boolean
            Return HasKeyboardFocus
        End Function

        Private Shared Function NormalizeText(value As String,
                                              fallback As String) As String
            Dim text As String = If(value, String.Empty).Trim()
            If text.Length = 0 Then Return If(fallback, String.Empty)
            Return text
        End Function

        Private Shared Function NormalizeQuery(value As String) As String
            Return If(value, String.Empty).TrimStart()
        End Function

        Private Shared Function CreateSizeContext(context As MASLayoutMeasureContext) As MASSizeContext
            If context Is Nothing Then Return MASIntrinsicControlSizeMetrics.EnsureContext(Nothing)
            Return context.ToSizeContext()
        End Function

        Private Shared Function InsetHorizontal(rect As SKRect,
                                                 amountPx As Single) As SKRect
            Return New SKRect(rect.Left + amountPx, rect.Top, rect.Right - amountPx, rect.Bottom)
        End Function

        Private Shared Function ResolvePanelColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.CardsTheme IsNot Nothing Then Return ctx.CardsTheme.Visuals.DefaultPalette.SurfaceMid
            If ctx IsNot Nothing AndAlso ctx.SurfaceTheme IsNot Nothing Then Return ctx.SurfaceTheme.SurfaceMid
            Return SKColors.White
        End Function

        Private Shared Function ResolveInputColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.InputTheme IsNot Nothing Then Return ctx.InputTheme.Surface.Mid
            Return SKColors.White
        End Function

        Private Shared Function ResolveTitleColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.TextColorTheme IsNot Nothing Then Return ctx.TextColorTheme.PrimaryText
            Return Nexamas.UI.Values.Color.Text.Primary
        End Function

        Private Shared Function ResolveMutedColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.TextColorTheme IsNot Nothing Then Return ctx.TextColorTheme.SecondaryText
            Return Nexamas.UI.Values.Color.Text.Secondary
        End Function

        Private Shared Function ResolveAccentColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.BrandTheme IsNot Nothing Then Return ctx.BrandTheme.BrandPrimary
            Return SKColors.RoyalBlue
        End Function

        Private Sub RaiseSelectionChangedSafe()
            Try
                RaiseEvent SelectionChanged(Me, EventArgs.Empty)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1, "MASCommandPalette.SelectionChanged")
            End Try
        End Sub

        Private Sub RaiseCommandExecutedSafe()
            Try
                RaiseEvent CommandExecuted(Me, EventArgs.Empty)
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException2, "MASCommandPalette.CommandExecuted")
            End Try
        End Sub

        Private Sub RaiseCommandsChangedSafe()
            Try
                RaiseEvent CommandsChanged(Me, EventArgs.Empty)
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException3, "MASCommandPalette.CommandsChanged")
            End Try
        End Sub

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            If _disposedLocal Then Return
            _disposedLocal = True
            If disposing Then
                StopCommandPaletteSelectionMotion(resetState:=True)
                _shadowPainter.Dispose()
            End If
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
