Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
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 step-progress control. It owns only ordered step state and
    ''' visual progress rendering. Wizard page/content orchestration is owned by
    ''' MASWizard so the stepper can also be used independently in forms and flows.
    ''' </summary>
    Partial Public NotInheritable Class MASStepper
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private NotInheritable Class StepInfo
            Friend Property Title As String
            Friend Property Description As String
            Friend Property State As MASStepperStepState

            Friend Sub New(title As String,
                           description As String)
                Me.Title = MASStepperFlowPolicy.NormalizeStepTitle(title)
                Me.Description = MASStepperFlowPolicy.NormalizeStepDescription(description)
                Me.State = MASStepperStepState.Pending
            End Sub
        End Class

        Private ReadOnly _steps As New List(Of StepInfo)()
        Private _orientation As MASStepperOrientation = MASStepperOrientation.Horizontal
        Private _currentIndex As Integer = -1
        Private _disposedLocal As Boolean

#End Region

#Region "Events"

        ''' <summary>
        ''' Raised after the current step changes through SetCurrentStep, MoveNext, or MovePrevious.
        ''' </summary>
        Public Event CurrentStepChanged As EventHandler

#End Region

#Region "Constructor / Factory"

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

        Public Shared Function Create(Optional orientation As MASStepperOrientation = MASStepperOrientation.Horizontal) As MASStepper
            Dim stepper As New MASStepper()
            stepper._orientation = MASStepperFlowPolicy.NormalizeOrientation(orientation)
            Return stepper
        End Function

        Friend Shared Function [Default]() As MASStepper
            Return Create()
        End Function

#End Region

#Region "Public API"

        Public Property Orientation As MASStepperOrientation
            Get
                Return _orientation
            End Get
            Set(value As MASStepperOrientation)
                Dim normalized As MASStepperOrientation = MASStepperFlowPolicy.NormalizeOrientation(value)
                If _orientation = normalized Then Return

                _orientation = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASStepper.Orientation")
                InvalidateVisual()
            End Set
        End Property

        Public Property CurrentIndex As Integer
            Get
                Return _currentIndex
            End Get
            Set(value As Integer)
                SetCurrentStep(value)
            End Set
        End Property

        Public ReadOnly Property StepCount As Integer
            Get
                Return _steps.Count
            End Get
        End Property

        Public Function AddStep(title As String,
                                Optional description As String = Nothing) As Integer
            Dim stepInfo As New StepInfo(title, description)
            _steps.Add(stepInfo)

            If _currentIndex < 0 Then _currentIndex = 0

            RequestSizeLayoutRefreshForSizeAffectingChange("MASStepper.AddStep")
            InvalidateVisual()
            Return _steps.Count - 1
        End Function

        Public Function RemoveStep(index As Integer) As MASStepper
            ValidateIndex(index)
            _steps.RemoveAt(index)

            Dim changed As Boolean = False
            If _steps.Count = 0 Then
                changed = (_currentIndex <> -1)
                _currentIndex = -1
            ElseIf _currentIndex >= _steps.Count Then
                _currentIndex = _steps.Count - 1
                changed = True
            ElseIf index <= _currentIndex AndAlso _currentIndex > 0 Then
                _currentIndex -= 1
                changed = True
            End If

            RequestSizeLayoutRefreshForSizeAffectingChange("MASStepper.RemoveStep")
            InvalidateVisual()
            If changed Then RaiseCurrentStepChangedSafe()
            Return Me
        End Function

        Public Function ClearSteps() As MASStepper
            If _steps.Count = 0 Then Return Me

            _steps.Clear()
            Dim changed As Boolean = (_currentIndex <> -1)
            _currentIndex = -1
            RequestSizeLayoutRefreshForSizeAffectingChange("MASStepper.ClearSteps")
            InvalidateVisual()
            If changed Then RaiseCurrentStepChangedSafe()
            Return Me
        End Function

        Public Function GetStepTitle(index As Integer) As String
            ValidateIndex(index)
            Return _steps(index).Title
        End Function

        Public Function SetStepText(index As Integer,
                                    title As String,
                                    Optional description As String = Nothing) As MASStepper
            ValidateIndex(index)
            _steps(index).Title = MASStepperFlowPolicy.NormalizeStepTitle(title)
            _steps(index).Description = MASStepperFlowPolicy.NormalizeStepDescription(description)
            RaiseTextChanged()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASStepper.SetStepText")
            InvalidateVisual()
            Return Me
        End Function

        Public Function GetStepState(index As Integer) As MASStepperStepState
            ValidateIndex(index)
            Return ResolveRenderState(index)
        End Function

        Public Function SetStepState(index As Integer,
                                     state As MASStepperStepState) As MASStepper
            ValidateIndex(index)
            _steps(index).State = MASStepperFlowPolicy.NormalizeState(state)
            InvalidateVisual()
            Return Me
        End Function

        Public Sub SetCurrentStep(index As Integer)
            If index = -1 AndAlso _steps.Count = 0 Then
                If _currentIndex <> -1 Then
                    _currentIndex = -1
                    InvalidateVisual()
                    RaiseCurrentStepChangedSafe()
                End If
                Return
            End If

            ValidateIndex(index)
            If _steps(index).State = MASStepperStepState.Disabled Then Return
            If _currentIndex = index Then Return

            Dim previousIndex As Integer = _currentIndex
            _currentIndex = index
            StartCurrentStepMotion(previousIndex, _currentIndex)
            InvalidateVisual()
            RaiseCurrentStepChangedSafe()
        End Sub

        Public Sub MoveNext()
            Dim index As Integer = MASStepperFlowPolicy.ResolveNextEnabledIndex(_currentIndex, _steps.Count, AddressOf IsStepEnabledForSelection)
            If index >= 0 AndAlso index <> _currentIndex Then SetCurrentStep(index)
        End Sub

        Public Sub MovePrevious()
            Dim index As Integer = MASStepperFlowPolicy.ResolvePreviousEnabledIndex(_currentIndex, _steps.Count, AddressOf IsStepEnabledForSelection)
            If index >= 0 AndAlso index <> _currentIndex Then SetCurrentStep(index)
        End Sub

        Public Function WithOrientation(value As MASStepperOrientation) As MASStepper
            Orientation = value
            Return Me
        End Function

        Public Function WithCurrentStep(index As Integer) As MASStepper
            SetCurrentStep(index)
            Return Me
        End Function

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

#End Region

#Region "Layout / size"

        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 SKSize = ResolveDesiredSize()
            Dim minimum As New SKSize(StepperTokens.StepperMinWidthDip,
                                      If(_orientation = MASStepperOrientation.Vertical,
                                         StepperTokens.StepperVerticalMinHeightDip,
                                         StepperTokens.StepperHorizontalHeightDip))

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

        Private Function ResolveDesiredSize() As SKSize
            Dim count As Integer = Math.Max(1, _steps.Count)
            If _orientation = MASStepperOrientation.Vertical Then
                Return New SKSize(StepperTokens.StepperMinWidthDip,
                                  Math.Max(StepperTokens.StepperVerticalMinHeightDip,
                                           count * StepperTokens.StepperVerticalStepHeightDip))
            End If

            Dim width As Single = Math.Max(StepperTokens.StepperMinWidthDip,
                                           (count * StepperTokens.StepTextMaxWidthDip) + ((count - 1) * StepperTokens.StepGapDip))
            Return New SKSize(width, StepperTokens.StepperHorizontalHeightDip)
        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

#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
            _contract.AssertConsumable()

            Dim plan As MASStepperLayoutPlan = MASStepperLayoutPlan.Create(ctx, pixelBounds, SizeIntent)
            Dim dpi As Single = plan.Dpi
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            If _orientation = MASStepperOrientation.Vertical Then
                DrawVertical(canvas, ctx, bounds, plan)
            Else
                DrawHorizontal(canvas, ctx, bounds, plan)
            End If
        End Sub

        Private Sub DrawHorizontal(canvas As SKCanvas,
                                   ctx As MASThemeContext,
                                   bounds As SKRect,
                                   plan As MASStepperLayoutPlan)
            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            Dim count As Integer = _steps.Count
            If count <= 0 Then Return

            Dim radius As Single = plan.RadiusPx
            Dim top As Single = bounds.Top + plan.VerticalEdgeInsetPx
            Dim centerY As Single = top + radius
            Dim left As Single = bounds.Left + radius + plan.HorizontalEdgeInsetPx
            Dim right As Single = bounds.Right - radius - plan.HorizontalEdgeInsetPx
            Dim usableWidth As Single = Math.Max(1.0F, right - left)
            Dim stepGap As Single = If(count = 1, 0.0F, usableWidth / CSng(count - 1))
            Dim isRtl As Boolean = MASStepperFlowPolicy.ResolveRightToLeft()

            For i As Integer = 0 To count - 2
                Dim x1 As Single = ResolveHorizontalStepX(left, stepGap, i, count, isRtl) + If(isRtl, -radius, radius)
                Dim x2 As Single = ResolveHorizontalStepX(left, stepGap, i + 1, count, isRtl) + If(isRtl, radius, -radius)
                DrawConnector(canvas, ctx, x1, centerY, x2, centerY, plan, i < _currentIndex)
            Next

            DrawCurrentStepMotionOverlay(canvas, ctx, bounds, plan)

            For i As Integer = 0 To count - 1
                Dim x As Single = ResolveHorizontalStepX(left, stepGap, i, count, isRtl)
                DrawStepNode(canvas, ctx, x, centerY, plan, i)
                If plan.ShowLabels Then
                    DrawStepText(canvas, ctx, i, New SKRect(x - plan.TextMaxWidthPx / 2.0F,
                                                            centerY + radius + plan.TextGapPx,
                                                            x + plan.TextMaxWidthPx / 2.0F,
                                                            bounds.Bottom), plan, True)
                End If
            Next
        End Sub

        Private Shared Function ResolveHorizontalStepX(left As Single,
                                                             stepGap As Single,
                                                             index As Integer,
                                                             count As Integer,
                                                             isRtl As Boolean) As Single
            Dim visualIndex As Integer = If(isRtl, count - 1 - index, index)
            Return left + stepGap * visualIndex
        End Function

        Private Sub DrawVertical(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 bounds As SKRect,
                                 plan As MASStepperLayoutPlan)
            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            Dim count As Integer = _steps.Count
            If count <= 0 Then Return

            Dim radius As Single = plan.RadiusPx
            Dim left As Single = bounds.Left + radius + plan.HorizontalEdgeInsetPx
            Dim top As Single = bounds.Top + radius + plan.VerticalEdgeInsetPx
            Dim stepHeight As Single = plan.VerticalStepHeightPx

            For i As Integer = 0 To count - 2
                Dim y1 As Single = top + stepHeight * i + radius
                Dim y2 As Single = top + stepHeight * (i + 1) - radius
                DrawConnector(canvas, ctx, left, y1, left, y2, plan, i < _currentIndex)
            Next

            DrawCurrentStepMotionOverlay(canvas, ctx, bounds, plan)

            For i As Integer = 0 To count - 1
                Dim y As Single = top + stepHeight * i
                DrawStepNode(canvas, ctx, left, y, plan, i)
                If plan.ShowLabels Then
                    DrawStepText(canvas, ctx, i, New SKRect(left + radius + plan.TextGapPx,
                                                            y - radius,
                                                            bounds.Right - plan.HorizontalEdgeInsetPx,
                                                            y + stepHeight - plan.VerticalEdgeInsetPx), plan, False)
                End If
            Next
        End Sub

        Private Sub DrawConnector(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  x1 As Single,
                                  y1 As Single,
                                  x2 As Single,
                                  y2 As Single,
                                  plan As MASStepperLayoutPlan,
                                  completed As Boolean)
            Using paint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = If(plan Is Nothing, 1.0F, plan.ConnectorStrokePx),
                .Color = If(completed, ResolveAccentColor(ctx).WithAlpha(StepperTokens.CompletedAlpha), ResolveMutedColor(ctx).WithAlpha(StepperTokens.ConnectorAlpha))
            }
                canvas.DrawLine(x1, y1, x2, y2, paint)
            End Using
        End Sub

        Private Sub DrawStepNode(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 x As Single,
                                 y As Single,
                                 plan As MASStepperLayoutPlan,
                                 index As Integer)
            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            Dim state As MASStepperStepState = ResolveRenderState(index)
            Dim radius As Single = plan.RadiusPx
            Dim fill As SKColor = ResolveStateColor(ctx, state)
            Dim stroke As SKColor = ResolveStrokeColor(ctx, state)

            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = fill}
                canvas.DrawCircle(x, y, radius, paint)
            End Using

            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, plan.ConnectorStrokePx * 0.7F), .Color = stroke}
                canvas.DrawCircle(x, y, radius, paint)
            End Using

            Dim label As String = ResolveNodeText(index, state)
            Dim rect As New SKRect(x - radius, y - radius, x + radius, y + radius)
            TypographyTextPrimitives.DrawSingleLineText(canvas,
                                                        ctx,
                                                        label,
                                                        rect,
                                                        dpi,
                                                        MASTypography.MASTextStyle.Small,
                                                        ResolveNodeTextColor(ctx, state),
                                                        TypographyTextPrimitives.TextAlign.Center,
                                                        False)
        End Sub

        Private Sub DrawStepText(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 index As Integer,
                                 rect As SKRect,
                                 plan As MASStepperLayoutPlan,
                                 center As Boolean)
            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            Dim title As String = _steps(index).Title
            Dim description As String = _steps(index).Description
            Dim align As TypographyTextPrimitives.TextAlign = If(center, TypographyTextPrimitives.TextAlign.Center, TypographyTextPrimitives.TextAlign.Left)
            Dim titleRect As New SKRect(rect.Left, rect.Top, rect.Right, rect.Top + StepperTokens.StepLabelHeightDip * dpi)
            TypographyTextPrimitives.DrawSingleLineText(canvas,
                                                        ctx,
                                                        title,
                                                        titleRect,
                                                        dpi,
                                                        MASTypography.MASTextStyle.Small,
                                                        ResolveTitleColor(ctx, ResolveRenderState(index)),
                                                        align,
                                                        False)

            If plan.ShowDescriptions AndAlso Not String.IsNullOrWhiteSpace(description) Then
                Dim bodyRect As New SKRect(rect.Left,
                                           titleRect.Bottom,
                                           rect.Right,
                                           titleRect.Bottom + StepperTokens.StepDescriptionHeightDip * dpi)
                TypographyTextPrimitives.DrawSingleLineText(canvas,
                                                            ctx,
                                                            description,
                                                            bodyRect,
                                                            dpi,
                                                            MASTypography.MASTextStyle.Small,
                                                            ResolveDescriptionColor(ctx, ResolveRenderState(index)),
                                                            align,
                                                            False)
            End If
        End Sub

        Private Function ResolveRenderState(index As Integer) As MASStepperStepState
            Return MASStepperFlowPolicy.ResolveRenderState(index, _currentIndex, _steps(index).State)
        End Function

        Private Shared Function ResolveNodeText(index As Integer,
                                                state As MASStepperStepState) As String
            If state = MASStepperStepState.Completed Then Return "✓"
            If state = MASStepperStepState.[Error] Then Return "!"
            Return (index + 1).ToString(System.Globalization.CultureInfo.InvariantCulture)
        End Function

        Private Shared Function ResolveStateColor(ctx As MASThemeContext,
                                                  state As MASStepperStepState) As SKColor
            Select Case state
                Case MASStepperStepState.Completed, MASStepperStepState.Current
                    Return ResolveAccentColor(ctx).WithAlpha(If(state = MASStepperStepState.Current, StepperTokens.CurrentAlpha, StepperTokens.CompletedAlpha))
                Case MASStepperStepState.[Error]
                    Return Nexamas.UI.Values.Color.Interaction.DangerTint.WithAlpha(StepperTokens.ErrorAlpha)
                Case MASStepperStepState.Disabled
                    Return ResolveMutedColor(ctx).WithAlpha(StepperTokens.DisabledAlpha)
                Case Else
                    Return ResolveMutedColor(ctx).WithAlpha(StepperTokens.SurfaceAlpha)
            End Select
        End Function

        Private Shared Function ResolveStrokeColor(ctx As MASThemeContext,
                                                   state As MASStepperStepState) As SKColor
            If state = MASStepperStepState.Pending OrElse state = MASStepperStepState.Disabled Then Return ResolveMutedColor(ctx).WithAlpha(StepperTokens.ConnectorAlpha)
            Return ResolveAccentColor(ctx).WithAlpha(StepperTokens.CurrentAlpha)
        End Function

        Private Shared Function ResolveNodeTextColor(ctx As MASThemeContext,
                                                     state As MASStepperStepState) As SKColor
            If state = MASStepperStepState.Pending OrElse state = MASStepperStepState.Disabled Then Return ResolveTitleFallbackColor(ctx).WithAlpha(StepperTokens.PendingAlpha)
            Return SKColors.White
        End Function

        Private Shared Function ResolveTitleColor(ctx As MASThemeContext,
                                                  state As MASStepperStepState) As SKColor
            If state = MASStepperStepState.Disabled Then Return ResolveMutedColor(ctx).WithAlpha(StepperTokens.DisabledAlpha)
            If state = MASStepperStepState.Current Then Return ResolveAccentColor(ctx).WithAlpha(StepperTokens.CurrentAlpha)
            Return ResolveTitleFallbackColor(ctx).WithAlpha(If(state = MASStepperStepState.Pending, StepperTokens.PendingAlpha, StepperTokens.CompletedAlpha))
        End Function

        Private Shared Function ResolveDescriptionColor(ctx As MASThemeContext,
                                                        state As MASStepperStepState) As SKColor
            If state = MASStepperStepState.Disabled Then Return ResolveMutedColor(ctx).WithAlpha(StepperTokens.DisabledAlpha)
            Return ResolveMutedColor(ctx).WithAlpha(If(state = MASStepperStepState.Pending, StepperTokens.PendingAlpha, StepperTokens.CompletedAlpha))
        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
            If ctx IsNot Nothing AndAlso ctx.SemanticTheme IsNot Nothing Then Return ctx.SemanticTheme.Info
            Return Nexamas.UI.Values.Color.Accent.Accent2
        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 ResolveTitleFallbackColor(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

#End Region

#Region "Helpers"

        Friend Function CreateStepperReadinessManifest() As MASStepperReadinessManifest
            Return MASStepperReadinessManifest.CreateCurrent()
        End Function

        Private Function IsStepEnabledForSelection(index As Integer) As Boolean
            Return index >= 0 AndAlso index < _steps.Count AndAlso _steps(index).State <> MASStepperStepState.Disabled
        End Function

        Private Sub ValidateIndex(index As Integer)
            If index < 0 OrElse index >= _steps.Count Then Throw New ArgumentOutOfRangeException(NameOf(index))
        End Sub

        Private Sub RaiseCurrentStepChangedSafe()
            Try
                RaiseEvent CurrentStepChanged(Me, EventArgs.Empty)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1, "MASStepper.CurrentStepChanged")
            End Try
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            If Not Enabled Then Return False

            Dim nextIndex As Integer = MASStepperFlowPolicy.ResolveKeyboardStepIndex(
                keyCode,
                _currentIndex,
                _steps.Count,
                _orientation,
                MASStepperFlowPolicy.ResolveRightToLeft(),
                AddressOf IsStepEnabledForSelection)

            If nextIndex >= 0 AndAlso nextIndex <> _currentIndex Then
                SetCurrentStep(nextIndex)
                Return True
            End If

            Return False
        End Function

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

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

#End Region

#Region "Dispose"

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

#End Region

    End Class

End Namespace
