Imports System.Windows.Media
Imports System
Imports System.Windows.Documents
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Shapes
Imports System.Windows.Media.Animation

''' <summary>
''' CursorAdorner is a visual representation of the tracking hand.
''' </summary>
Public Class CursorAdorner
    Inherits Adorner
    ' local members for managing cursor visuals
    Private ReadOnly _adorningElement As UIElement
    Private _visualChildren As VisualCollection
    Private _cursorCanvas As Canvas
    Protected _cursor As FrameworkElement
    Private _isVisible As Boolean
    Private _isOverridden As Boolean
    Private _gradientStopAnimationStoryboard As Storyboard

    ' default cursor colors
    Private Shared ReadOnly _backColor As Color = Colors.White
    Private Shared ReadOnly _foreColor As Color = Colors.Gray


    ''' <summary>
    ''' Initializes a new instance of the <see cref="CursorAdorner"/> class.
    ''' </summary>
    ''' <param name="adorningElement">The adorning element.</param>
    Public Sub New(adorningElement As FrameworkElement)
        MyBase.New(adorningElement)
        If adorningElement Is Nothing Then
            Throw New ArgumentNullException("adorningElement")
        End If
        Me._adorningElement = adorningElement
        CreateCursorAdorner()
        Me.IsHitTestVisible = False
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the <see cref="CursorAdorner"/> class.
    ''' </summary>
    ''' <param name="adorningElement">The adorning element.</param>
    ''' <param name="innerCursor">The inner cursor.</param>
    Public Sub New(adorningElement As FrameworkElement, innerCursor As FrameworkElement)
        MyBase.New(adorningElement)
        If adorningElement Is Nothing Then
            Throw New ArgumentNullException("Adorning Element parameter empty")
        End If
        Me._adorningElement = adorningElement
        CreateCursorAdorner(innerCursor)
        Me.IsHitTestVisible = False
    End Sub

    ''' <summary>
    ''' Gets the cursor visual.
    ''' </summary>
    Public ReadOnly Property CursorVisual As FrameworkElement
        Get
            Return _cursor
        End Get
    End Property

    ''' <summary>
    ''' Creates the cursor adorner.
    ''' </summary>
    Public Sub CreateCursorAdorner()
        Dim innerCursor As FrameworkElement = CreateCursor()
        CreateCursorAdorner(innerCursor)
    End Sub

    ''' <summary>
    ''' Creates the cursor.
    ''' </summary>
    ''' <returns></returns>
    Protected Function CreateCursor() As FrameworkElement
        Dim Brush As New LinearGradientBrush
        Brush.EndPoint = New Point(0, 1)
        Brush.StartPoint = New Point(0, 0)
        Brush.GradientStops.Add(New GradientStop(_backColor, 1))
        Brush.GradientStops.Add(New GradientStop(_foreColor, 1))
        Dim cursor As New Ellipse() With {
            .Width = 50,
            .Height = 50,
            .Fill = Brush
        }
        Return cursor
    End Function

    ''' <summary>
    ''' Creates the cursor adorner.
    ''' </summary>
    ''' <param name="innerCursor">The inner cursor.</param>
    Public Sub CreateCursorAdorner(innerCursor As FrameworkElement)
        _visualChildren = New VisualCollection(Me)
        _cursorCanvas = New Canvas()
        _cursor = innerCursor
        _cursorCanvas.Children.Add(_cursor)
        _visualChildren.Add(Me._cursorCanvas)
        Dim layer As AdornerLayer = AdornerLayer.GetAdornerLayer(_adorningElement)
        layer.Add(Me)
    End Sub

    ''' <summary>
    ''' Updates the cursor position.
    ''' </summary>
    ''' <param name="position">The position.</param>
    ''' <param name="isOverride">if set to <c>true</c> [is override].</param>
    Public Sub UpdateCursor(position As Point, isOverride As Boolean)
        _isOverridden = isOverride
        ' center the cursor visual at the new position
        _cursor.SetValue(Canvas.LeftProperty, position.X - (_cursor.ActualWidth / 2))
        _cursor.SetValue(Canvas.TopProperty, position.Y - (_cursor.ActualHeight / 2))
    End Sub

    ''' <summary>
    ''' Updates the cursor position.
    ''' </summary>
    ''' <param name="position">The position.</param>
    Public Sub UpdateCursor(position As Point)
        If (_isOverridden) Then
            Exit Sub
        End If
        ' center the cursor visual at the new position
        _cursor.SetValue(Canvas.LeftProperty, position.X - (_cursor.ActualWidth / 2))
        _cursor.SetValue(Canvas.TopProperty, position.Y - (_cursor.ActualHeight / 2))
    End Sub

#Region "オーバーライドメソッド"

    ''' <summary>
    ''' Gets the number of visual child elements within Me element.
    ''' </summary>
    ''' <returns>The number of visual child elements for Me element.</returns>
    Protected Overrides ReadOnly Property VisualChildrenCount As Integer
        Get
            Return _visualChildren.Count
        End Get
    End Property

    ''' <summary>
    ''' Overrides <see cref="M:System.Windows.Media.Visual.GetVisualChild(System.Int32)"/>, and returns a child at the specified index from a collection of child elements.
    ''' </summary>
    ''' <param name="index">The zero-based index of the requested child element in the collection.</param>
    ''' <returns>
    ''' The requested child element. Me should not return Nothing if the provided index is out of range, an exception is thrown.
    ''' </returns>
    Protected Overrides Function GetVisualChild(index As Integer) As Visual
        Return _visualChildren(index)
    End Function

    ''' <summary>
    ''' Implements any custom measuring behavior for the adorner.
    ''' </summary>
    ''' <param name="constraint">A size to constrain the adorner to.</param>
    ''' <returns>
    ''' A <see cref="T:System.Windows.Size"/> object representing the amount of layout space needed by the adorner.
    ''' </returns>
    Protected Overrides Function MeasureOverride(constraint As Size) As Size
        Me._cursorCanvas.Measure(constraint)
        Return Me._cursorCanvas.DesiredSize
    End Function

    ''' <summary>
    ''' When overridden in a derived class, positions child elements and determines a size for a <see cref="T:System.Windows.FrameworkElement"/> derived class.
    ''' </summary>
    ''' <param name="finalSize">The final area within the parent that Me element should use to arrange itself and its children.</param>
    ''' <returns>
    ''' The actual size used.
    ''' </returns>
    Protected Overrides Function ArrangeOverride(finalSize As Size) As Size
        Me._cursorCanvas.Arrange(New Rect(finalSize))
        Return finalSize
    End Function

    ''' <summary>
    ''' Sets the visibility of the cursor visual.
    ''' </summary>
    ''' <param name="isVisible">if set to <c>true</c> [is visible].</param>
    Public Sub SetVisibility(isVisible As Boolean)
        If Me._isVisible AndAlso Not isVisible Then
            _cursorCanvas.Visibility = Visibility.Hidden
        End If
        If Not Me._isVisible AndAlso isVisible Then
            _cursorCanvas.Visibility = Visibility.Visible
        End If
        Me._isVisible = isVisible
    End Sub

    ''' <summary>
    ''' Animates the cursor.
    ''' </summary>
    ''' <param name="milliSeconds">The milli seconds.</param>
    Public Overridable Sub AnimateCursor(milliSeconds As Double)
        CreateGradientStopAnimation(milliSeconds)
        If _gradientStopAnimationStoryboard IsNot Nothing Then
            _gradientStopAnimationStoryboard.Begin(Me, True)
        End If
    End Sub

    ''' <summary>
    ''' Stops the cursor animation.
    ''' </summary>
    Public Overridable Sub StopCursorAnimation()
        If _gradientStopAnimationStoryboard IsNot Nothing Then
            _gradientStopAnimationStoryboard.Stop(Me)
        End If
    End Sub

    ''' <summary>
    ''' Creates the gradient stop animation.
    ''' </summary>
    ''' <param name="milliSeconds">The milli seconds.</param>
    Protected Overridable Sub CreateGradientStopAnimation(milliSeconds As Double)
        NameScope.SetNameScope(Me, New NameScope())

        Dim cursor As Shape = CType(_cursor, Shape)
        If cursor Is Nothing Then
            Exit Sub
        End If
        Dim brush As LinearGradientBrush = CType(cursor.Fill, LinearGradientBrush)
        Dim stop1 As System.Windows.Media.GradientStop = brush.GradientStops(0)
        Dim stop2 As System.Windows.Media.GradientStop = brush.GradientStops(1)
        Me.RegisterName("GradientStop1", stop1)
        Me.RegisterName("GradientStop2", stop2)

        Dim offsetAnimation As DoubleAnimation = New DoubleAnimation()
        offsetAnimation.From = 1.0
        offsetAnimation.To = 0.0
        offsetAnimation.Duration = TimeSpan.FromMilliseconds(milliSeconds)

        Storyboard.SetTargetName(offsetAnimation, "GradientStop1")
        Storyboard.SetTargetProperty(offsetAnimation,
                                     New PropertyPath(GradientStop.OffsetProperty))


        Dim offsetAnimation2 As DoubleAnimation = New DoubleAnimation()
        offsetAnimation2.From = 1.0
        offsetAnimation2.To = 0.0
        offsetAnimation2.Duration = TimeSpan.FromMilliseconds(milliSeconds)

        Storyboard.SetTargetName(offsetAnimation2, "GradientStop2")
        Storyboard.SetTargetProperty(offsetAnimation2,
                                     New PropertyPath(GradientStop.OffsetProperty))

        _gradientStopAnimationStoryboard = New Storyboard()
        _gradientStopAnimationStoryboard.Children.Add(offsetAnimation)
        _gradientStopAnimationStoryboard.Children.Add(offsetAnimation2)
        AddHandler _gradientStopAnimationStoryboard.Completed, Sub()
                                                                   _gradientStopAnimationStoryboard.Stop(Me)
                                                               End Sub

    End Sub
#End Region
End Class
