using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Animation; using Tango.SharedUI.Helpers; namespace Tango.SharedUI.Controls { [ContentProperty(nameof(Elements))] public class NavigationControl : UserControl { #region Transition Types public enum TransitionTypes { Fade, Zoom, Slide, } #endregion #region Navigation Element private class NavigationElement : ContentControl { public FrameworkElement Element { get { return (FrameworkElement)GetValue(ElementProperty); } set { SetValue(ElementProperty, value); } } public static readonly DependencyProperty ElementProperty = DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(NavigationControl), new PropertyMetadata(null)); public ScaleTransform Scale { get { return (ScaleTransform)GetValue(ScaleProperty); } set { SetValue(ScaleProperty, value); } } public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register("Scale", typeof(ScaleTransform), typeof(NavigationControl), new PropertyMetadata(null)); public RotateTransform Rotate { get { return (RotateTransform)GetValue(RotateProperty); } set { SetValue(RotateProperty, value); } } public static readonly DependencyProperty RotateProperty = DependencyProperty.Register("Rotate", typeof(RotateTransform), typeof(NavigationControl), new PropertyMetadata(null)); public TranslateTransform Translate { get { return (TranslateTransform)GetValue(TranslateProperty); } set { SetValue(TranslateProperty, value); } } public static readonly DependencyProperty TranslateProperty = DependencyProperty.Register("Translate", typeof(TranslateTransform), typeof(NavigationControl), new PropertyMetadata(null)); public NavigationElement() { Visibility = Visibility.Hidden; Scale = new ScaleTransform(1, 1); Rotate = new RotateTransform(0); Translate = new TranslateTransform(0, 0); TransformGroup group = new TransformGroup(); group.Children.Add(Scale); group.Children.Add(Rotate); group.Children.Add(Translate); RenderTransformOrigin = new Point(0.5, 0.5); RenderTransform = group; } public NavigationElement(FrameworkElement element) : this() { Element = element; } public void AnimateScale(DoubleAnimation animation) { Scale.BeginAnimation(ScaleTransform.ScaleXProperty, animation); Scale.BeginAnimation(ScaleTransform.ScaleYProperty, animation); } public void AnimateTranslateX(DoubleAnimation animation) { Translate.BeginAnimation(TranslateTransform.XProperty, animation); } public void AnimateOpacity(DoubleAnimation animation) { BeginAnimation(OpacityProperty, animation); } public void Reset() { Scale.BeginAnimation(ScaleTransform.ScaleXProperty, null); Scale.BeginAnimation(ScaleTransform.ScaleYProperty, null); Rotate.BeginAnimation(RotateTransform.AngleProperty, null); Translate.BeginAnimation(TranslateTransform.XProperty, null); Translate.BeginAnimation(TranslateTransform.YProperty, null); Opacity = 1; } public void Activate() { if (Content != Element) { Content = Element; } Visibility = Visibility.Visible; } public void Deactivate(bool detach) { if (detach) { Content = null; } Visibility = Visibility.Hidden; } } #endregion #region INavigationView public interface INavigationView { void OnNavigated(); } #endregion private Grid _grid; private bool _loaded; #region Properties public ObservableCollection Elements { get { return (ObservableCollection)GetValue(ElementsProperty); } set { SetValue(ElementsProperty, value); } } public static readonly DependencyProperty ElementsProperty = DependencyProperty.Register("Elements", typeof(ObservableCollection), typeof(NavigationControl), new PropertyMetadata(null, (d, e) => (d as NavigationControl).OnElementsChanged())); public FrameworkElement SelectedElement { get { return (FrameworkElement)GetValue(SelectedElementProperty); } set { SetValue(SelectedElementProperty, value); } } public static readonly DependencyProperty SelectedElementProperty = DependencyProperty.Register("SelectedElement", typeof(FrameworkElement), typeof(NavigationControl), new PropertyMetadata(null, (d, e) => (d as NavigationControl).OnSelectedElementChanged(e.OldValue as FrameworkElement, e.NewValue as FrameworkElement))); public TransitionTypes TransitionType { get { return (TransitionTypes)GetValue(TransitionTypeProperty); } set { SetValue(TransitionTypeProperty, value); } } public static readonly DependencyProperty TransitionTypeProperty = DependencyProperty.Register("TransitionType", typeof(TransitionTypes), typeof(NavigationControl), new PropertyMetadata(TransitionTypes.Zoom)); public bool TransitionAlwaysFades { get { return (bool)GetValue(TransitionAlwaysFadesProperty); } set { SetValue(TransitionAlwaysFadesProperty, value); } } public static readonly DependencyProperty TransitionAlwaysFadesProperty = DependencyProperty.Register("TransitionAlwaysFades", typeof(bool), typeof(NavigationControl), new PropertyMetadata(false)); public Duration TransitionDuration { get { return (Duration)GetValue(TransitionDurationProperty); } set { SetValue(TransitionDurationProperty, value); } } public static readonly DependencyProperty TransitionDurationProperty = DependencyProperty.Register("TransitionDuration", typeof(Duration), typeof(NavigationControl), new PropertyMetadata(new Duration(TimeSpan.FromSeconds(0.5)))); public bool KeepElementsAttached { get { return (bool)GetValue(KeepElementsAttachedProperty); } set { SetValue(KeepElementsAttachedProperty, value); } } public static readonly DependencyProperty KeepElementsAttachedProperty = DependencyProperty.Register("KeepElementsAttached", typeof(bool), typeof(NavigationControl), new PropertyMetadata(false)); #endregion #region Attached Properties #region Draggable /// /// Determines the element navigation name. /// public static readonly DependencyProperty NavigationName = DependencyProperty.RegisterAttached("NavigationName", typeof(String), typeof(NavigationControl), new FrameworkPropertyMetadata("")); /// /// Sets the name of the navigation. /// /// The element. /// The value. public static void SetNavigationName(FrameworkElement element, String value) { element.SetValue(NavigationName, value); } /// /// Gets the name of the navigation. /// /// The element. /// public static String GetNavigationName(FrameworkElement element) { return element.GetValue(NavigationName).ToString(); } #endregion #endregion #region Constructors public NavigationControl() { ClipToBounds = true; _grid = new Grid(); Content = _grid; Elements = new ObservableCollection(); Loaded += NavigationControl_Loaded; } #endregion #region Event Handlers private void NavigationControl_Loaded(object sender, RoutedEventArgs e) { if (!_loaded) { if (Elements != null) { SelectedElement = Elements.FirstOrDefault(); } _loaded = true; } } private void Elements_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { SyncElements(); } #endregion #region Private Methods private void OnElementsChanged() { if (Elements != null) { Elements.CollectionChanged -= Elements_CollectionChanged; Elements.CollectionChanged += Elements_CollectionChanged; } SyncElements(); } private void SyncElements() { if (Elements == null) { _grid.Children.Clear(); } else { var toRemove = _grid.Children.OfType().ToList().Where(x => !Elements.Contains(x.Element)).ToList(); var toAdd = Elements.Where(x => !_grid.Children.OfType().Select(y => y.Element).ToList().Contains(x)).ToList(); toRemove.ForEach(x => _grid.Children.Remove(x)); toAdd.ForEach(x => _grid.Children.Add( new NavigationElement() { RenderTransformOrigin = new Point(0.5, 0.5), Element = x, Content = KeepElementsAttached ? x : null, })); } if (!Elements.Contains(SelectedElement)) { SelectedElement = null; } } private NavigationElement GetElementContainer(FrameworkElement element) { return _grid.Children.OfType().ToList().SingleOrDefault(x => x.Element == element); } private void Navigate(NavigationElement fromElement, NavigationElement toElement) { if (toElement == null) return; if (toElement == fromElement) return; if (fromElement != null) { DoubleAnimation toAnimation = new DoubleAnimation(); toAnimation.Duration = TransitionDuration; DoubleAnimation fromAnimation = new DoubleAnimation(); fromAnimation.Duration = TransitionDuration; int fromIndex = Elements.IndexOf(fromElement.Element); int toIndex = Elements.IndexOf(toElement.Element); fromElement.Reset(); toElement.Reset(); fromAnimation.Completed += (_, __) => { fromElement.Deactivate(!KeepElementsAttached); }; toAnimation.Completed += (_, __) => { INavigationView navigationView = toElement.Element as INavigationView; if (navigationView != null) { navigationView.OnNavigated(); } }; switch (TransitionType) { case TransitionTypes.Fade: fromAnimation.From = 1; fromAnimation.To = 0; toAnimation.From = 0; toAnimation.To = 1; fromElement.AnimateOpacity(fromAnimation); toElement.AnimateOpacity(toAnimation); break; case TransitionTypes.Zoom: fromAnimation.From = 1; fromAnimation.To = 0; toAnimation.From = 0; toAnimation.To = 1; fromElement.AnimateScale(fromAnimation); toElement.AnimateScale(toAnimation); break; case TransitionTypes.Slide: if (toIndex > fromIndex) { fromAnimation.From = 0; fromAnimation.To = -ActualWidth; toAnimation.From = ActualWidth; toAnimation.To = 0; } else { fromAnimation.From = 0; fromAnimation.To = ActualWidth; toAnimation.From = -ActualWidth; toAnimation.To = 0; } fromElement.AnimateTranslateX(fromAnimation); toElement.AnimateTranslateX(toAnimation); break; } if (TransitionAlwaysFades && TransitionType != TransitionTypes.Fade) { DoubleAnimation fromFadeAnimation = new DoubleAnimation(); fromFadeAnimation.From = 1; fromFadeAnimation.To = 0; fromFadeAnimation.Duration = TransitionDuration; DoubleAnimation toFadeAnimation = new DoubleAnimation(); toFadeAnimation.From = 0; toFadeAnimation.To = 1; toFadeAnimation.Duration = TransitionDuration; fromElement.AnimateOpacity(fromFadeAnimation); toElement.AnimateOpacity(toFadeAnimation); } fromElement.Activate(); toElement.Activate(); } else { toElement.Activate(); toElement.Reset(); } } #endregion #region Properties Changes protected virtual void OnSelectedElementChanged(FrameworkElement fromElement, FrameworkElement toElement) { Navigate(GetElementContainer(fromElement), GetElementContainer(toElement)); } #endregion #region Public Methods public void NavigateTo(FrameworkElement element) { SelectedElement = element; } public void NavigateTo(String navigationName) { SelectedElement = Elements.SingleOrDefault(x => GetNavigationName(x) == navigationName); } public FrameworkElement GetAndDetach(String navigationName) { var element = Elements.SingleOrDefault(x => GetNavigationName(x) == navigationName); GetElementContainer(element).Deactivate(!KeepElementsAttached); return element; } #endregion } }