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.Forms.Integration; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using Tango.SharedUI.Helpers; namespace Tango.SharedUI.Controls { [ContentProperty(nameof(Elements))] public class NavigationControl : UserControl { private Action _onCompleted; #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 OnNavigatedTo(); void OnNavigatedFrom(); } #endregion #region INavigationViewModel /// /// Represents a view data context which can be notified when its view is being navigated in or out. /// public interface INavigationViewModel { /// /// Called when the navigation system has navigated to this VM view. /// void OnNavigatedTo(); /// /// Called when the navigation system has navigated from this VM view. /// void OnNavigatedFrom(); } #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)); public int SelectedIndex { get { return (int)GetValue(SelectedIndexProperty); } set { SetValue(SelectedIndexProperty, value); } } // Using a DependencyProperty as the backing store for SelectedIndex. This enables animation, styling, binding, etc... public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register("SelectedIndex", typeof(int), typeof(NavigationControl), new PropertyMetadata(0, (d, e) => (d as NavigationControl).OnSelectedIndexChanged())); public bool UseDefferedRendering { get { return (bool)GetValue(UseDefferedRenderingProperty); } set { SetValue(UseDefferedRenderingProperty, value); } } public static readonly DependencyProperty UseDefferedRenderingProperty = DependencyProperty.Register("UseDefferedRendering", typeof(bool), typeof(NavigationControl), new PropertyMetadata(false)); private void OnSelectedIndexChanged() { SelectedElement = Elements[SelectedIndex > Elements.Count - 1 ? 0 : SelectedIndex]; } #endregion #region Attached Properties #region NavigationName /// /// 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).ToStringSafe(); } #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) { _loaded = true; if (Elements != null) { //foreach (var element in Elements.Where(x => x != SelectedElement)) //{ // LoadVisual(element); //} SelectedElement = Elements.FirstOrDefault(); } } } 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)); if (UseDefferedRendering) { foreach (var x in toAdd) { var navigationElement = new NavigationElement() { RenderTransformOrigin = new Point(0.5, 0.5), Element = x, Content = x, }; _grid.Children.Add(navigationElement); if (!KeepElementsAttached) { bool loaded = false; navigationElement.Loaded += (_, __) => { if (!loaded) { if (x != SelectedElement) { loaded = true; navigationElement.Content = null; } } }; } } } else { 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 || toElement == fromElement) { _onCompleted?.Invoke(); } 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); }; bool completed = false; toAnimation.Completed += (_, __) => { if (!completed) { completed = true; INavigationView fromNavigationView = fromElement.Element as INavigationView; INavigationView toNavigationView = toElement.Element as INavigationView; if (fromNavigationView != null) { fromNavigationView.OnNavigatedFrom(); } if (toNavigationView != null) { toNavigationView.OnNavigatedTo(); } INavigationViewModel fromVM = fromElement.Element.DataContext as INavigationViewModel; INavigationViewModel toVM = toElement.Element.DataContext as INavigationViewModel; if (fromVM != null && fromVM != toVM) fromVM.OnNavigatedFrom(); if (toVM != null) toVM.OnNavigatedTo(); _onCompleted?.Invoke(); } }; 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(); INavigationViewModel toVM = toElement.Element.DataContext as INavigationViewModel; if (toVM != null) toVM.OnNavigatedTo(); } } #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) { var e = Elements.SingleOrDefault(x => x == element); if (e != null) { SelectedElement = element; } } public FrameworkElement NavigateTo(String navigationName) { return NavigateTo(navigationName, null); } public FrameworkElement NavigateTo(String navigationName, Action onCompleted = null) { _onCompleted = onCompleted; var element = Elements.SingleOrDefault(x => GetNavigationName(x) == navigationName || x.GetType().Name == navigationName); if (element != null) { if (SelectedElement == element) { _onCompleted?.Invoke(); } else { SelectedElement = element; } } return element; } public FrameworkElement GetElement(String navigationName) { return Elements.SingleOrDefault(x => GetNavigationName(x) == navigationName || x.GetType().Name == navigationName); } public FrameworkElement GetAndDetach(String navigationName) { var element = Elements.SingleOrDefault(x => GetNavigationName(x) == navigationName); GetElementContainer(element).Deactivate(!KeepElementsAttached); return element; } /// /// This method needs to be called in order for // the element to print visibly at the correct size. /// private static void LoadVisual(FrameworkElement element) { if (element.Parent == null) { try { System.Windows.Forms.UserControl controlContainer = new System.Windows.Forms.UserControl(); controlContainer.Width = 100; controlContainer.Height = 100; var host = new ElementHost() { Child = element, Dock = System.Windows.Forms.DockStyle.Fill }; controlContainer.Controls.Add(host); IntPtr handle = controlContainer.Handle; host.Child = null; controlContainer.Dispose(); } catch { } } } #endregion } }