view SilverlightGlimpse/SilverFlow.Controls/FloatingWindow/IconBar.cs @ 64:ba89e36631bc

Latest version
author Steven Hollidge <stevenhollidge@hotmail.com>
date Sun, 22 Apr 2012 14:20:52 +0100
parents 536498832a79
children
line wrap: on
line source

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using SilverFlow.Controls.Extensions;
using SilverFlow.Controls.Helpers;

namespace SilverFlow.Controls
{
    /// <summary>
    /// IconBar containing window icons.
    /// </summary>
    [TemplatePart(Name = PART_LayoutRoot, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = PART_FixedBar, Type = typeof(Border))]
    [TemplatePart(Name = PART_SlidingBar, Type = typeof(Border))]
    [TemplatePart(Name = PART_Carousel, Type = typeof(StackPanel))]
    [TemplateVisualState(Name = VSMSTATE_StateOpen, GroupName = VSMGROUP_States)]
    [TemplateVisualState(Name = VSMSTATE_StateClosed, GroupName = VSMGROUP_States)]
    [StyleTypedProperty(Property = PROPERTY_TitleStyle, StyleTargetType = typeof(Border))]
    [StyleTypedProperty(Property = PROPERTY_WindowIconStyle, StyleTargetType = typeof(WindowIcon))]
    public class IconBar : ContentControl, INotifyPropertyChanged
    {
        // Template parts
        private const string PART_LayoutRoot = "PART_LayoutRoot";
        private const string PART_FixedBar = "PART_FixedBar";
        private const string PART_SlidingBar = "PART_SlidingBar";
        private const string PART_Carousel = "PART_Carousel";

        // VSM groups
        private const string VSMGROUP_States = "VisualStateGroup";

        // VSM states
        private const string VSMSTATE_StateOpen = "Open";
        private const string VSMSTATE_StateClosed = "Closed";

        // Style typed properties
        private const string PROPERTY_TitleStyle = "IconBarStyle";
        private const string PROPERTY_WindowIconStyle = "WindowIconStyle";

        // Animation duration in milliseconds
        private const double SlidingDurationInMilliseconds = 200;

        #region public Style IconBarStyle

        /// <summary>
        /// Gets or sets the style of the IconBar.
        /// </summary>
        public Style IconBarStyle
        {
            get { return GetValue(IconBarStyleProperty) as Style; }
            set { SetValue(IconBarStyleProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="IconBar.IconBarStyleProperty" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty IconBarStyleProperty =
            DependencyProperty.Register(
                "IconBarStyle",
                typeof(Style),
                typeof(IconBar),
                new PropertyMetadata(IconBarStylePropertyChanged));

        /// <summary>
        /// IconBarStyle PropertyChangedCallback call back static function.
        /// </summary>
        /// <param name="d">IconBar object whose IconBarStyle property is changed.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void IconBarStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            IconBar iconBar = (IconBar)d;
            if (iconBar != null && iconBar.fixedBar != null)
            {
                iconBar.fixedBar.Style = e.NewValue as Style;
            }
        }

        #endregion

        #region public Style WindowIconStyle

        /// <summary>
        /// Gets or sets the style of the WindowIcon.
        /// </summary>
        public Style WindowIconStyle
        {
            get { return GetValue(WindowIconStyleProperty) as Style; }
            set { SetValue(WindowIconStyleProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="IconBar.WindowIconStyleProperty" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty WindowIconStyleProperty =
            DependencyProperty.Register(
                "WindowIconStyle",
                typeof(Style),
                typeof(IconBar),
                null);

        #endregion

        private FrameworkElement layoutRoot;
        private Border fixedBar;
        private Border slidingBar;
        private StackPanel carousel;
        private Storyboard closingStoryboard;
        private Storyboard openingStoryboard;
        private bool isOpen;
        private double slidingBarPosition;
        private IVisualHelper visualHelper;

        /// <summary>
        /// Gets or sets a value indicating whether the IconBar is open.
        /// </summary>
        /// <value><c>true</c> if the IconBar is open; otherwise, <c>false</c>.</value>
        public bool IsOpen
        {
            get { return isOpen; }
            private set
            {
                if (value != isOpen)
                {
                    isOpen = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("IsOpen"));
                }
            }
        }

        /// <summary>
        /// Gets or sets the FloatingWindowHost containing the IconBar.
        /// </summary>
        /// <value>FloatingWindowHost containing the IconBar.</value>
        public FloatingWindowHost FloatingWindowHost { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="IconBar"/> class.
        /// </summary>
        public IconBar()
        {
            DefaultStyleKey = typeof(IconBar);
            visualHelper = new VisualHelper();
        }

        /// <summary>
        /// Occurs when the <see cref="IconBar" /> is opened.
        /// </summary>
        public event EventHandler Opened;

        /// <summary>
        /// Occurs when the <see cref="IconBar" /> is closed.
        /// </summary>
        public event EventHandler Closed;

        #region INotifyPropertyChanged implementation
        /// <summary>
        /// Occurs when a property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the <see cref="E:PropertyChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        public void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }
        #endregion

        /// <summary>
        /// Builds the visual tree for the <see cref="IconBar" /> control 
        /// when a new template is applied.
        /// </summary>
        public override void OnApplyTemplate()
        {
            UnsubscribeFromEvents();

            base.OnApplyTemplate();

            layoutRoot = GetTemplatePart<FrameworkElement>(PART_LayoutRoot);
            fixedBar = GetTemplatePart<Border>(PART_FixedBar);
            slidingBar = GetTemplatePart<Border>(PART_SlidingBar);
            carousel = GetTemplatePart<StackPanel>(PART_Carousel);

            SetStyles();
            GetStoryboards();
            SubscribeToEvents();
        }

        /// <summary>
        /// Shows the IconBar.
        /// </summary>
        public void Show()
        {
            if (!IsOpen)
            {
                FillCarousel();
                SetSlidingBarPosition(0);
                VisualStateManager.GoToState(this, VSMSTATE_StateOpen, true);
            }
        }

        /// <summary>
        /// Hides the IconBar.
        /// </summary>
        public void Hide()
        {
            if (IsOpen)
            {
                VisualStateManager.GoToState(this, VSMSTATE_StateClosed, true);
            }
        }

        /// <summary>
        /// Updates the IconBar if it is open.
        /// </summary>
        public void Update()
        {
            if (IsOpen)
            {
                FillCarousel();
            }
        }

        /// <summary>
        /// Removes the specified window from the IconBar.
        /// </summary>
        /// <param name="window">The window to remove from the IconBar.</param>
        public void Remove(FloatingWindow window)
        {
            if (window != null)
            {
                var icon = (from windowIcon in carousel.Children.OfType<WindowIcon>()
                            where windowIcon.Window == window
                            select windowIcon).FirstOrDefault();

                if (icon != null)
                {
                    icon.Click -= new RoutedEventHandler(Icon_Click);
                    carousel.Children.Remove(icon);
                }
            }
        }

        /// <summary>
        /// Gets the storyboards defined in the <see cref="IconBar" /> style.
        /// </summary>
        private void GetStoryboards()
        {
            var groups = VisualStateManager.GetVisualStateGroups(layoutRoot) as Collection<VisualStateGroup>;
            if (groups != null)
            {
                var states = (from stategroup in groups
                              where stategroup.Name == IconBar.VSMGROUP_States
                              select stategroup.States).FirstOrDefault() as Collection<VisualState>;

                if (states != null)
                {
                    openingStoryboard = (from state in states
                                         where state.Name == IconBar.VSMSTATE_StateOpen
                                         select state.Storyboard).FirstOrDefault();

                    closingStoryboard = (from state in states
                                         where state.Name == IconBar.VSMSTATE_StateClosed
                                         select state.Storyboard).FirstOrDefault();
                }
            }
        }

        /// <summary>
        /// Subscribes to the events after new template is applied. 
        /// </summary>
        private void SubscribeToEvents()
        {
            if (closingStoryboard != null)
                closingStoryboard.Completed += new EventHandler(Closing_Completed);

            if (openingStoryboard != null)
                openingStoryboard.Completed += new EventHandler(Opening_Completed);

            if (fixedBar != null)
                fixedBar.MouseMove += new MouseEventHandler(Bar_MouseMove);

            if (fixedBar != null)
                fixedBar.SizeChanged += new SizeChangedEventHandler(FixedBar_SizeChanged);
        }

        /// <summary>
        /// Unsubscribe from events. 
        /// </summary>
        private void UnsubscribeFromEvents()
        {
            if (closingStoryboard != null)
                closingStoryboard.Completed -= new EventHandler(Closing_Completed);

            if (openingStoryboard != null)
                openingStoryboard.Completed -= new EventHandler(Opening_Completed);

            if (fixedBar != null)
                fixedBar.MouseMove += new MouseEventHandler(Bar_MouseMove);

            if (fixedBar != null)
                fixedBar.SizeChanged -= new SizeChangedEventHandler(FixedBar_SizeChanged);
        }

        /// <summary>
        /// Sets styles that are applied for different template parts.
        /// </summary>
        private void SetStyles()
        {
            if (fixedBar != null && this.IconBarStyle != null)
                fixedBar.Style = this.IconBarStyle;
        }

        /// <summary>
        /// Executed when the Closing storyboard ends.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event args.</param>
        private void Closing_Completed(object sender, EventArgs e)
        {
            IsOpen = false;
            ClearCarousel();
            OnClosed(EventArgs.Empty);
        }

        /// <summary>
        /// Executed when the Opening storyboard finishes.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event args.</param>
        private void Opening_Completed(object sender, EventArgs e)
        {
            IsOpen = true;
            OnOpened(EventArgs.Empty);
        }

        /// <summary>
        /// Raises the <see cref="E:Opened"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnOpened(EventArgs e)
        {
            EventHandler handler = Opened;

            if (handler != null)
                handler(this, e);
        }

        /// <summary>
        /// Raises the <see cref="E:Closed"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnClosed(EventArgs e)
        {
            EventHandler handler = Closed;

            if (handler != null)
                handler(this, e);
        }

        /// <summary>
        /// Add windows icons to the carousel.
        /// </summary>
        private void FillCarousel()
        {
            ClearCarousel();

            Style style = this.WindowIconStyle ?? Application.Current.Resources["WindowIconStyle"] as Style;
            foreach (var window in this.FloatingWindowHost.WindowsInIconBar)
            {
                WindowIcon icon = new WindowIcon()
                {
                    Style = style,
                    Title = window.IconText,
                    Thumbnail = window.WindowThumbnail,
                    FlowDirection = window.FlowDirection,
                    Window = window,
                    IconWidth = this.FloatingWindowHost.IconWidth,
                    IconHeight = this.FloatingWindowHost.IconHeight
                };

                icon.Click += new RoutedEventHandler(Icon_Click);
                carousel.Children.Add(icon);
            }
        }

        /// <summary>
        /// Remove Icon Click event handlers and clear the carousel.
        /// </summary>
        private void ClearCarousel()
        {
            foreach (var icon in carousel.Children.OfType<WindowIcon>())
            {
                icon.Click -= new RoutedEventHandler(Icon_Click);
            }

            carousel.Children.Clear();
        }

        /// <summary>
        /// Handles the Click event of the Icon control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Icon_Click(object sender, RoutedEventArgs e)
        {
            WindowIcon icon = sender as WindowIcon;

            if (icon != null && icon.Window != null)
            {
                this.Hide();
                icon.Window.RestoreWindow();
            }
        }

        /// <summary>
        /// Handles the MouseMove event of the Bar control. It implements "Carousel" logic.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        private void Bar_MouseMove(object sender, MouseEventArgs e)
        {
            // Get absolute mouse position
            Point mousePosition = e.GetPosition(null);

            var icon = carousel.Children.OfType<WindowIcon>().FirstOrDefault();

            // If there is at least one icon on the bar
            if (icon != null)
            {
                double a = e.GetPosition(fixedBar).X;
                double b = fixedBar.ActualWidth - fixedBar.Padding.Horizontal();
                double c = slidingBar.ActualWidth;

                // If sliding bar does not fit into the bar, shift the sliding bar
                if (c > b)
                {
                    double width = b - icon.ActualWidth;
                    if (width != 0)
                    {
                        a -= icon.ActualWidth / 2;
                        if (a < 0) a = 0;
                        if (a > width) a = width;

                        double x = Math.Round((a / width) * (b - c));

                        if (x != slidingBarPosition)
                        {
                            Storyboard storyboard = slidingBar.AnimateDoubleProperty("(Canvas.Left)", null, x, SlidingDurationInMilliseconds);
                            storyboard.Completed += (s, args) =>
                                {
                                    // Select an icon on storyboard completion
                                    // That is necessary because MouseOver state won't be proceeded correctly
                                    SlidingBarStoryboardCompleted(mousePosition);
                                    slidingBarPosition = x;
                                };
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Handles the Completed event of the SlidingBarStoryboard control.
        /// </summary>
        /// <param name="mousePosition">Absolute mouse position.</param>
        private void SlidingBarStoryboardCompleted(Point mousePosition)
        {
            // Find selected icon
            var selectedIcon = (from item in visualHelper.FindElementsInCoordinates(mousePosition, carousel).OfType<WindowIcon>()
                                select item).FirstOrDefault();

            // Select an icon in mouse position
            foreach (var icon in carousel.Children.OfType<WindowIcon>())
            {
                icon.Selected = selectedIcon != null && icon == selectedIcon;
            }
        }

        /// <summary>
        /// Handles the SizeChanged event of the FixedBar control.
        /// Sets the initial position of the sliding bar.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.SizeChangedEventArgs"/> instance containing the event data.</param>
        private void FixedBar_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (slidingBarPosition != 0)
                SetSlidingBarPosition(0);
        }

        /// <summary>
        /// Sets the sliding bar position.
        /// </summary>
        /// <param name="position">X-coordinate of the sliding bar.</param>
        private void SetSlidingBarPosition(double x)
        {
            slidingBarPosition = x;
            Canvas.SetLeft(slidingBar, slidingBarPosition);
        }

        /// <summary>
        /// Gets the FrameworkElement template part with the specified name.
        /// </summary>
        /// <typeparam name="T">The template part type.</typeparam>
        /// <param name="partName">The template part name.</param>
        /// <returns>The requested element.</returns>
        /// <exception cref="NotImplementedException">The template part not found.</exception>
        private T GetTemplatePart<T>(string partName) where T : class
        {
            T part = this.GetTemplateChild(partName) as T;

            if (part == null)
                throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "Template Part {0} is required.", partName));

            return part;
        }
    }
}