// (c) Copyright Microsoft Corporation. 
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved. 

using System;
using System.Diagnostics; 
using System.Windows; 
using System.Windows.Input;
using System.Windows.Markup; 
using System.Windows.Media.Animation;
using System.Windows.Controls.Primitives;
using System.Windows.Media; 
using System.Windows.Automation.Peers;
using System.Windows.Controls;
 
namespace System.Windows.Controls
{ 
    /// <summary>
    /// Slider control lets the user select from a range of values by moving a slider.
    /// Slider is used to enable to user to gradually modify a value (range selection). 
    /// Slider is an easy and natural interface for users, because it provides good visual feedback.
    /// </summary>
    [TemplatePart(Name = Slider.ElementHorizontalTemplateName, Type = typeof(FrameworkElement))] 
    [TemplatePart(Name = Slider.ElementHorizontalLargeIncreaseName, Type = typeof(RepeatButton))] 
    [TemplatePart(Name = Slider.ElementHorizontalLargeDecreaseName, Type = typeof(RepeatButton))]
    [TemplatePart(Name = Slider.ElementHorizontalThumbName, Type = typeof(Thumb))] 
    [TemplatePart(Name = Slider.ElementVerticalTemplateName, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = Slider.ElementVerticalLargeIncreaseName, Type = typeof(RepeatButton))]
    [TemplatePart(Name = Slider.ElementVerticalLargeDecreaseName, Type = typeof(RepeatButton))] 
    [TemplatePart(Name = Slider.ElementVerticalThumbName, Type = typeof(Thumb))]
    [TemplateVisualState(Name = VisualStates.StateNormal, GroupName = VisualStates.GroupCommon)]
    [TemplateVisualState(Name = VisualStates.StateMouseOver, GroupName = VisualStates.GroupCommon)] 
    [TemplateVisualState(Name = VisualStates.StateDisabled, GroupName = VisualStates.GroupCommon)] 
    [TemplateVisualState(Name = VisualStates.StateUnfocused, GroupName = VisualStates.GroupFocus)]
    [TemplateVisualState(Name = VisualStates.StateFocused, GroupName = VisualStates.GroupFocus)] 
    public partial class Slider : RangeBase
    {
        #region Constructor 

        /// <summary>
        /// Constructor to setup the Slider class 
        /// </summary> 
        public Slider()
        { 
            SizeChanged += delegate { UpdateTrackLayout(); };

            DefaultStyleKey = typeof(Slider); 
            IsEnabledChanged += OnIsEnabledChanged;
        }
 
        /// <summary> 
        /// Apply a template to the slider.
        /// </summary> 
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate(); 

            // Get the parts
            ElementHorizontalTemplate = GetTemplateChild(ElementHorizontalTemplateName) as FrameworkElement; 
            ElementHorizontalLargeIncrease = GetTemplateChild(ElementHorizontalLargeIncreaseName) as RepeatButton; 
            ElementHorizontalLargeDecrease = GetTemplateChild(ElementHorizontalLargeDecreaseName) as RepeatButton;
            ElementHorizontalThumb = GetTemplateChild(ElementHorizontalThumbName) as Thumb; 
            ElementVerticalTemplate = GetTemplateChild(ElementVerticalTemplateName) as FrameworkElement;
            ElementVerticalLargeIncrease = GetTemplateChild(ElementVerticalLargeIncreaseName) as RepeatButton;
            ElementVerticalLargeDecrease = GetTemplateChild(ElementVerticalLargeDecreaseName) as RepeatButton; 
            ElementVerticalThumb = GetTemplateChild(ElementVerticalThumbName) as Thumb;

            if (ElementHorizontalThumb != null) 
            { 
                ElementHorizontalThumb.DragStarted += delegate(object sender, DragStartedEventArgs e) { this.Focus(); OnThumbDragStarted(); };
                ElementHorizontalThumb.DragDelta += delegate(object sender, DragDeltaEventArgs e) { OnThumbDragDelta(e); }; 
            }
            if (ElementHorizontalLargeDecrease != null)
            { 
                ElementHorizontalLargeDecrease.Click += delegate { this.Focus(); Value -= LargeChange; };
            }
            if (ElementHorizontalLargeIncrease != null) 
            { 
                ElementHorizontalLargeIncrease.Click += delegate { this.Focus(); Value += LargeChange; };
            } 

            if (ElementVerticalThumb != null)
            { 
                ElementVerticalThumb.DragStarted += delegate(object sender, DragStartedEventArgs e) { this.Focus(); OnThumbDragStarted(); };
                ElementVerticalThumb.DragDelta += delegate(object sender, DragDeltaEventArgs e) { OnThumbDragDelta(e); };
            } 
            if (ElementVerticalLargeDecrease != null) 
            {
                ElementVerticalLargeDecrease.Click += delegate { this.Focus(); Value -= LargeChange; }; 
            }
            if (ElementVerticalLargeIncrease != null)
            { 
                ElementVerticalLargeIncrease.Click += delegate { this.Focus(); Value += LargeChange; };
            }
            // Updating states for parts where properties might have been updated through 
            // XAML before the template was loaded. 
            OnOrientationChanged();
            ChangeVisualState(false); 
        }
        #endregion Constructor
 
        #region Orientation
        /// <summary>
        /// Gets whether the Slider has an orientation of vertical or horizontal. 
        /// </summary> 
        public Orientation Orientation
        { 
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        } 

        /// <summary>
        /// Identifies the Orientation dependency property. 
        /// </summary> 
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.RegisterCore( 
                "Orientation",
                typeof(Orientation),
                typeof(Slider), 
                new PropertyMetadata(Orientation.Horizontal, OnOrientationPropertyChanged));

        /// <summary> 
        /// OrientationProperty property changed handler. 
        /// </summary>
        /// <param name="d">Slider that changed Orientation.</param> 
        /// <param name="e">DependencyPropertyChangedEventArgs.</param>
        private static void OnOrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        { 
            Slider s = d as Slider;
            Debug.Assert(s != null);
 
            s.OnOrientationChanged(); 
        }
 
        #endregion Orientation

        #region IsFocused 
        /// <summary>
        /// Gets a value that determines whether this element has logical focus.
        /// </summary> 
        public bool IsFocused 
        {
            get { return (bool)GetValue(IsFocusedProperty); } 
            internal set { SetValueImpl(IsFocusedProperty, value); }
        }
 
        /// <summary>
        /// Identifies the IsFocused dependency property.
        /// </summary> 
        public static readonly DependencyProperty IsFocusedProperty = 
            DependencyProperty.RegisterReadOnlyCore(
                "IsFocused", 
                typeof(bool),
                typeof(Slider),
                new PropertyMetadata(OnIsFocusedPropertyChanged)); 

        /// <summary>
        /// IsFocusedProperty property changed handler. 
        /// </summary> 
        /// <param name="d">Slider that changed IsFocused.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Slider s = d as Slider; 
            Debug.Assert(s != null);

            s.OnIsFocusChanged(e); 
        } 

        /// <summary> 
        /// Called when the IsFocused property changes.
        /// </summary>
        /// <param name="e"> 
        /// The data for DependencyPropertyChangedEventArgs.
        /// </param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        internal virtual void OnIsFocusChanged(DependencyPropertyChangedEventArgs e) 
        {
            UpdateVisualState(); 
        }

        #endregion IsFocused 

        #region IsDirectionReversed
        /// <summary> 
        /// Gets a value that determines whether the direction is reversed. 
        /// </summary>
        public bool IsDirectionReversed 
        {
            get { return (bool)GetValue(IsDirectionReversedProperty); }
            set { SetValue(IsDirectionReversedProperty, value); } 
        }

        /// <summary> 
        /// Identifies the IsDirectionReversed dependency property. 
        /// </summary>
        public static readonly DependencyProperty IsDirectionReversedProperty = 
            DependencyProperty.RegisterCore(
                "IsDirectionReversed",
                typeof(bool), 
                typeof(Slider),
                new PropertyMetadata(OnIsDirectionReversedChanged));
 
        /// <summary> 
        /// IsDirectionReversedProperty property changed handler.
        /// </summary> 
        /// <param name="d">Slider that changed IsDirectionReversed.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param>
        private static void OnIsDirectionReversedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
        {
            Slider s = d as Slider;
            Debug.Assert(s != null); 
 
            s.UpdateTrackLayout();
 
        }

        #endregion IsDirectionReversed 

        #region IsEnabled
 
        /// <summary> 
        /// Called when the IsEnabled property changes.
        /// </summary> 
        /// <param name="e">Property changed args</param>
        private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
        { 
            if (!IsEnabled)
            {
                IsMouseOver = false; 
            } 

            UpdateVisualState(); 
        }

        #endregion IsEnabled 

        #region OverridePropertyChanges
 
        /// <summary> 
        /// Called when the Value property changes.
        /// </summary> 
        /// <param name="oldValue">Old value of the Value property.</param>
        /// <param name="newValue">New value of the Value property.</param>
        protected override void OnValueChanged(double oldValue, double newValue) 
        {
            base.OnValueChanged(oldValue, newValue);
            UpdateTrackLayout(); 
        } 

        /// <summary> 
        /// Called when the Minimum property changes.
        /// </summary>
        /// <param name="oldMinimum">Old value of the Minimum property.</param> 
        /// <param name="newMinimum">New value of the Minimum property.</param>
        protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
        { 
            base.OnMinimumChanged(oldMinimum, newMinimum); 
            UpdateTrackLayout();
        } 

        /// <summary>
        /// Called when the Maximum property changes. 
        /// </summary>
        /// <param name="oldMaximum">Old value of the Maximum property.</param>
        /// <param name="newMaximum">New value of the Maximum property.</param> 
        protected override void OnMaximumChanged(double oldMaximum, double newMaximum) 
        {
            base.OnMaximumChanged(oldMaximum, newMaximum); 
            UpdateTrackLayout();
        }
 
        #endregion OverridePropertyChanges

        #region MouseEvents 
        /// <summary> 
        /// Responds to the MouseEnter event.
        /// </summary> 
        /// <param name="e">The event data for the MouseEnter event.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnMouseEnter(MouseEventArgs e) 
        {
            base.OnMouseEnter(e);
            IsMouseOver = true; 
            if ((Orientation == Orientation.Horizontal && ElementHorizontalThumb != null && !ElementHorizontalThumb.IsDragging) || 
                (Orientation == Orientation.Vertical && ElementVerticalThumb != null && !ElementVerticalThumb.IsDragging))
            { 
                UpdateVisualState();
            }
        } 

        /// <summary>
        /// Responds to the MouseLeave event. 
        /// </summary> 
        /// <param name="e">The event data for the MouseLeave event.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e); 
            IsMouseOver = false;
            if ((Orientation == Orientation.Horizontal && ElementHorizontalThumb != null && !ElementHorizontalThumb.IsDragging) ||
                (Orientation == Orientation.Vertical && ElementVerticalThumb != null && !ElementVerticalThumb.IsDragging)) 
            { 
                UpdateVisualState();
            } 
        }

        /// <summary> 
        /// Responds to the MouseLeftButtonDown event.
        /// </summary>
        /// <param name="e">The event data for the MouseLeftButtonDown event.</param> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        { 
            base.OnMouseLeftButtonDown(e);
            if (e.Handled)
            { 
                return;
            }
            e.Handled = true; 
            Focus(); 
            CaptureMouse();
        } 

        /// <summary>
        /// Responds to the MouseLeftButtonUp event. 
        /// </summary>
        /// <param name="e">The event data for the MouseLeftButtonUp event.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) 
        {
            base.OnMouseLeftButtonUp(e); 
            if (e.Handled)
            {
                return; 
            }
            e.Handled = true;
            ReleaseMouseCapture(); 
            UpdateVisualState(); 
        }
        #endregion MouseEvents 

        #region KeyEvents
        /// <summary> 
        /// Responds to the KeyPressed event.
        /// </summary>
        /// <param name="e">The event data for the KeyPressed event.</param> 
        protected override void OnKeyDown(KeyEventArgs e) 
        {
            base.OnKeyDown(e); 
            if (e.Handled)
            {
                return; 
            }

            if (!IsEnabled) 
            { 
                return;
            } 

            if (e.Key == Key.Left || e.Key == Key.Down)
            { 
                if (IsDirectionReversed)
                {
                    Value += SmallChange; 
                } 
                else
                { 
                    Value -= SmallChange;
                }
            } 
            else if (e.Key == Key.Right || e.Key == Key.Up)
            {
                if (IsDirectionReversed) 
                { 
                    Value -= SmallChange;
                } 
                else
                {
                    Value += SmallChange; 
                }
            }
            else if (e.Key == Key.Home) 
            { 
                Value = Minimum;
            } 
            else if (e.Key == Key.End)
            {
                Value = Maximum; 
            }
        }
        #endregion KeyEvents 
 
        #region Focus Handlers
        protected override void OnGotFocus(RoutedEventArgs e) 
        {
            base.OnGotFocus(e);
            IsFocused = true; 
        }

        protected override void OnLostFocus(RoutedEventArgs e) 
        { 
            base.OnLostFocus(e);
            IsFocused = false; 
        }
        #endregion
 
        #region Change State
        /// <summary>
        /// Update the current visual state of the slider. 
        /// </summary> 
        internal void UpdateVisualState()
        { 
            ChangeVisualState(true);
        }
 
        /// <summary>
        /// Change to the correct visual state for the Slider.
        /// </summary> 
        /// <param name="useTransitions"> 
        /// true to use transitions when updating the visual state, false to
        /// snap directly to the new visual state. 
        /// </param>
        internal void ChangeVisualState(bool useTransitions)
        { 
            if (!IsEnabled)
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateDisabled, VisualStates.StateNormal); 
            } 
            else if (IsMouseOver)
            { 
                VisualStates.GoToState(this, useTransitions, VisualStates.StateMouseOver, VisualStates.StateNormal);
            }
            else 
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateNormal);
            } 
 
            if (IsFocused && IsEnabled)
            { 
                VisualStates.GoToState(this, useTransitions, VisualStates.StateFocused, VisualStates.StateUnfocused);
            }
            else 
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateUnfocused);
            } 
        } 
        #endregion Change State
 
        #region ThumbDragDelta

        /// <summary> 
        /// Called whenever the Thumb drag operation is started
        /// </summary>
        private void OnThumbDragStarted() 
        { 
            this._dragValue = this.Value;
        } 

        /// <summary>
        /// Whenever the thumb gets dragged, we handle the event through 
        /// this function to update the current value depending upon the
        /// thumb drag delta.
        /// </summary> 
        /// <param name="e">DragEventArgs</param> 
        private void OnThumbDragDelta(DragDeltaEventArgs e)
        { 
            double offset = 0;

            if (Orientation == Orientation.Horizontal && ElementHorizontalThumb != null) 
            {
                offset = e.HorizontalChange / (ActualWidth - ElementHorizontalThumb.ActualWidth) * (Maximum - Minimum);
            } 
            else if (Orientation == Orientation.Vertical && ElementVerticalThumb != null) 
            {
                offset = -e.VerticalChange / (ActualHeight - ElementVerticalThumb.ActualHeight) * (Maximum - Minimum); 
            }

            if (!double.IsNaN(offset) && !double.IsInfinity(offset)) 
            {
                _dragValue += IsDirectionReversed ? -offset : offset;
 
                double newValue = Math.Min(Maximum, Math.Max(Minimum, _dragValue)); 

                if (newValue != Value) 
                {
                    Value = newValue;
                } 
            }
        }
 
        #endregion ThumbDragDelta 

        #region UpdateTrackLayout 

        /// <summary>
        /// This code will run whenever Orientation changes, to change the template 
        /// being used to display this control.
        /// </summary>
        internal virtual void OnOrientationChanged() 
        { 
            if (ElementHorizontalTemplate != null)
            { 
                ElementHorizontalTemplate.Visibility = (Orientation == Orientation.Horizontal ? Visibility.Visible : Visibility.Collapsed);
            }
            if (ElementVerticalTemplate != null) 
            {
                ElementVerticalTemplate.Visibility = (Orientation == Orientation.Horizontal ? Visibility.Collapsed : Visibility.Visible);
            } 
            UpdateTrackLayout(); 
        }
 
        /// <summary>
        /// This method will take the current min, max, and value to
        /// calculate and layout the current control measurements. 
        /// </summary>
        internal virtual void UpdateTrackLayout()
        { 
            double maximum = Maximum; 
            double minimum = Minimum;
            double value = Value; 
            double multiplier = 1 - (maximum - value) / (maximum - minimum);

            Grid templateGrid = (Orientation == Orientation.Horizontal) ? (ElementHorizontalTemplate as Grid) : (ElementVerticalTemplate as Grid); 
            if (templateGrid != null)
            {
                if (Orientation == Orientation.Horizontal) 
                { 
                    if(templateGrid.ColumnDefinitions != null && templateGrid.ColumnDefinitions.Count == 3)
                    { 
                        templateGrid.ColumnDefinitions[0].Width = new GridLength(1, IsDirectionReversed ? GridUnitType.Star : GridUnitType.Auto);
                        templateGrid.ColumnDefinitions[2].Width = new GridLength(1, IsDirectionReversed ? GridUnitType.Auto : GridUnitType.Star);
                        if (ElementHorizontalLargeDecrease != null) 
                        {
                            ElementHorizontalLargeDecrease.SetValue(Grid.ColumnProperty, IsDirectionReversed ? 2 : 0);
                        } 
                        if (ElementHorizontalLargeIncrease != null) 
                        {
                            ElementHorizontalLargeIncrease.SetValue(Grid.ColumnProperty, IsDirectionReversed ? 0 : 2); 
                        }
                    }
 
                    if (ElementHorizontalLargeDecrease != null && ElementHorizontalThumb != null)
                    {
                        ElementHorizontalLargeDecrease.Width = Math.Max(0, multiplier * (ActualWidth - ElementHorizontalThumb.ActualWidth)); 
                    } 
                }
                else 
                {
                    if (templateGrid.RowDefinitions != null && templateGrid.RowDefinitions.Count == 3)
                    { 
                        templateGrid.RowDefinitions[0].Height = new GridLength(1, IsDirectionReversed ? GridUnitType.Auto : GridUnitType.Star);
                        templateGrid.RowDefinitions[2].Height = new GridLength(1, IsDirectionReversed ? GridUnitType.Star : GridUnitType.Auto);
                        if (ElementVerticalLargeDecrease != null) 
                        { 
                            ElementVerticalLargeDecrease.SetValue(Grid.RowProperty, IsDirectionReversed ? 0 : 2);
                        } 
                        if (ElementVerticalLargeIncrease != null)
                        {
                            ElementVerticalLargeIncrease.SetValue(Grid.RowProperty, IsDirectionReversed ? 2 : 0); 
                        }
                    }
 
                    if (ElementVerticalLargeDecrease != null && ElementVerticalThumb != null) 
                    {
                        ElementVerticalLargeDecrease.Height = multiplier * (ActualHeight - ElementVerticalThumb.ActualHeight); 
                    }
                }
            } 
        }

        #endregion UpdateTrackLayout 
 
        #region Template Parts
        /// <summary> 
        /// Horizontal template root
        /// </summary>
        internal virtual FrameworkElement ElementHorizontalTemplate { get; set; } 
        internal const string ElementHorizontalTemplateName = "HorizontalTemplate";

        /// <summary> 
        /// Large increase repeat button 
        /// </summary>
        internal virtual RepeatButton ElementHorizontalLargeIncrease { get; set; } 
        internal const string ElementHorizontalLargeIncreaseName = "HorizontalTrackLargeChangeIncreaseRepeatButton";

        /// <summary> 
        /// Large decrease repeat button
        /// </summary>
        internal virtual RepeatButton ElementHorizontalLargeDecrease { get; set; } 
        internal const string ElementHorizontalLargeDecreaseName = "HorizontalTrackLargeChangeDecreaseRepeatButton"; 

        /// <summary> 
        /// Thumb for dragging track
        /// </summary>
        internal virtual Thumb ElementHorizontalThumb { get; set; } 
        internal const string ElementHorizontalThumbName = "HorizontalThumb";

        /// <summary> 
        /// Vertical template root 
        /// </summary>
        internal virtual FrameworkElement ElementVerticalTemplate { get; set; } 
        internal const string ElementVerticalTemplateName = "VerticalTemplate";

        /// <summary> 
        /// Large increase repeat button
        /// </summary>
        internal virtual RepeatButton ElementVerticalLargeIncrease { get; set; } 
        internal const string ElementVerticalLargeIncreaseName = "VerticalTrackLargeChangeIncreaseRepeatButton"; 

        /// <summary> 
        /// Large decrease repeat button
        /// </summary>
        internal virtual RepeatButton ElementVerticalLargeDecrease { get; set; } 
        internal const string ElementVerticalLargeDecreaseName = "VerticalTrackLargeChangeDecreaseRepeatButton";

        /// <summary> 
        /// Thumb for dragging track 
        /// </summary>
        internal virtual Thumb ElementVerticalThumb { get; set; } 
        internal const string ElementVerticalThumbName = "VerticalThumb";

 
        #endregion Template Parts

        #region Member Variables 
        /// <summary> 
        /// Whether the mouse is currently over the control
        /// </summary> 
        internal bool IsMouseOver { get; set; }

        /// <summary> 
        /// Accumulates drag offsets in case the mouse drags off the end of the track.
        /// </summary>
        private double _dragValue; 
        #endregion Member Variables 

        #region AutomationPeer section

        protected override AutomationPeer OnCreateAutomationPeer ()
        {
            return new SliderAutomationPeer (this);
        }

        #endregion
    }
} 
