using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; using Tango.Core.Commands; using Tango.Core.EventArguments; using Tango.Core.ExtensionMethods; namespace Tango.Touch.Controls { public class TouchNotificationBar : ContentControl { private const double HEIGHT_RATIO = 0.75; private bool _isMouseDown; private Point _mouseDownLocation; private bool _isMoving; private Point _downLocation; private TouchDevice _touchDevice; private double _last_manipulation_delta; private DateTime _last_delta_measure_time; private double _last_mouse_y; private Border _border_notifications; private Border _border_drag; private Grid _grid_container; private Grid _grid_mask; private ItemsControl _items_control; private Grid _notification_counter_grid; private LightTouchScrollViewer _scrollViewer; static TouchNotificationBar() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TouchNotificationBar), new FrameworkPropertyMetadata(typeof(TouchNotificationBar))); } public String ItemExpandedPropertyPath { get { return (String)GetValue(ItemExpandedPropertyPathProperty); } set { SetValue(ItemExpandedPropertyPathProperty, value); } } public static readonly DependencyProperty ItemExpandedPropertyPathProperty = DependencyProperty.Register("ItemExpandedPropertyPath", typeof(String), typeof(TouchNotificationBar), new PropertyMetadata(null)); public bool HasNotifications { get { return (bool)GetValue(HasNotificationsProperty); } set { SetValue(HasNotificationsProperty, value); } } public static readonly DependencyProperty HasNotificationsProperty = DependencyProperty.Register("HasNotifications", typeof(bool), typeof(TouchNotificationBar), new PropertyMetadata(false, (d, e) => (d as TouchNotificationBar).OnHasNotificationsChanged())); public IList Notifications { get { return (IList)GetValue(NotificationsProperty); } set { SetValue(NotificationsProperty, value); } } public static readonly DependencyProperty NotificationsProperty = DependencyProperty.Register("Notifications", typeof(IList), typeof(TouchNotificationBar), new PropertyMetadata(null, (d, e) => (d as TouchNotificationBar).OnNotificationsChanged())); public DataTemplate NotificationTemplate { get { return (DataTemplate)GetValue(NotificationTemplateProperty); } set { SetValue(NotificationTemplateProperty, value); } } public static readonly DependencyProperty NotificationTemplateProperty = DependencyProperty.Register("NotificationTemplate", typeof(DataTemplate), typeof(TouchNotificationBar), new PropertyMetadata(null)); public double CurrentMinHeight { get { return (double)GetValue(CurrentMinHeightProperty); } set { SetValue(CurrentMinHeightProperty, value); } } public static readonly DependencyProperty CurrentMinHeightProperty = DependencyProperty.Register("CurrentMinHeight", typeof(double), typeof(TouchNotificationBar), new PropertyMetadata(0.0)); public bool IsExpanded { get { return (bool)GetValue(IsExpandedProperty); } set { SetValue(IsExpandedProperty, value); } } public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(TouchNotificationBar), new PropertyMetadata(false)); public RelayCommand NotificationPressedCommand { get { return (RelayCommand)GetValue(NotificationPressedCommandProperty); } set { SetValue(NotificationPressedCommandProperty, value); } } public static readonly DependencyProperty NotificationPressedCommandProperty = DependencyProperty.Register("NotificationPressedCommand", typeof(RelayCommand), typeof(TouchNotificationBar), new PropertyMetadata(null)); public Visibility NotificationBarVisibility { get { return (Visibility)GetValue(NotificationBarVisibilityProperty); } set { SetValue(NotificationBarVisibilityProperty, value); } } public static readonly DependencyProperty NotificationBarVisibilityProperty = DependencyProperty.Register("NotificationBarVisibility", typeof(Visibility), typeof(TouchNotificationBar), new PropertyMetadata(Visibility.Visible)); public TouchNotificationBar() { Loaded += TouchNotificationBar_Loaded; NotificationPressedCommand = new RelayCommand(() => { if (IsExpanded && !_scrollViewer.IsAfterScrolling) { CloseNotifications(); } }); } private void TouchNotificationBar_Loaded(object sender, RoutedEventArgs e) { _border_drag.MaxHeight = ActualHeight; } public override void OnApplyTemplate() { base.OnApplyTemplate(); _border_notifications = GetTemplateChild("PART_BorderNotifications") as Border; _border_drag = GetTemplateChild("PART_BorderDrag") as Border; _grid_container = GetTemplateChild("PART_Grid_Container") as Grid; _grid_mask = GetTemplateChild("PART_GridMask") as Grid; _items_control = GetTemplateChild("PART_ItemsControl") as ItemsControl; _notification_counter_grid = GetTemplateChild("PART_NotificationCounterGrid") as Grid; _scrollViewer = GetTemplateChild("PART_ScrollViewer") as LightTouchScrollViewer; //border_notifications.IsManipulationEnabled = true; _border_drag.RegisterForPreviewMouseOrTouchDown(GridNotificationMouseDown); _border_drag.RegisterForMouseOrTouchMove(GridNotificationMouseMove); _border_drag.RegisterForPreviewMouseOrTouchUp(GridNotificationMouseUp); _border_drag.SizeChanged += Drag_Border_SizeChanged; _items_control.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; } private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (_items_control.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated) { if (_items_control.Items.Count > 0) { var element = _items_control.ItemContainerGenerator.ContainerFromIndex(0) as FrameworkElement; if (element != null) { element.RegisterForLoadedOrNow((x, y) => { CurrentMinHeight = element.ActualHeight; ResizeNotification(element); }); } } else { CurrentMinHeight = 0; } } } private FrameworkElement GetLastNotification() { return _items_control.ItemContainerGenerator.ContainerFromIndex(0) as FrameworkElement; } private void Drag_Border_SizeChanged(object sender, SizeChangedEventArgs e) { _grid_mask.Opacity = _border_drag.ActualHeight / ActualHeight; ResizeNotifications(); if (e.NewSize.Height > CurrentMinHeight && CurrentMinHeight > 0) { _notification_counter_grid.Visibility = Visibility.Hidden; } else { _notification_counter_grid.Visibility = Visibility.Visible; } } private void ResizeNotifications() { for (int i = 0; i < _items_control.Items.Count; i++) { FrameworkElement element = _items_control.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; ResizeNotification(element); } } private void ResizeNotification(FrameworkElement element) { var control = element.FindChild(); if (control != null && !double.IsNaN(_border_drag.Height)) { control.Height = ((_border_drag.Height - CurrentMinHeight) / ActualHeight) * (control.MaxHeight - control.MinHeight) + control.MinHeight; } } private void OnHasNotificationsChanged() { DoubleAnimation ani = new DoubleAnimation(); ani.Duration = TimeSpan.FromSeconds(0.2); _grid_container.EnsureHeight(); if (HasNotifications) { var last = GetLastNotification(); last.RegisterForLoadedOrNow((x, e) => { ani.To = last.ActualHeight; _grid_container.BeginAnimation(Grid.HeightProperty, ani); _border_notifications.Height = last.ActualHeight; }); } else { CurrentMinHeight = 0; ani.To = 0; CloseNotifications(); _grid_container.BeginAnimation(Grid.HeightProperty, ani); } } private void OnNotificationsChanged() { UpdateExpanded(IsExpanded); if (Notifications is INotifyCollectionChanged) { (Notifications as INotifyCollectionChanged).CollectionChanged += (_, __) => { this.BeginInvoke(() => { UpdateExpanded(IsExpanded); }); }; } } private void UpdateExpanded(bool expanded) { if (ItemExpandedPropertyPath != null) { foreach (var item in Notifications.Cast().ToList()) { item.SetPropertyValueByPath(ItemExpandedPropertyPath, expanded); } } } private void OpenNotifications() { _border_drag.EnsureHeight(); _border_notifications.EnsureHeight(); _border_drag.StartDoubleAnimation(Border.HeightProperty, TimeSpan.FromSeconds(0.2), _border_drag.MaxHeight, null, null, 1, () => { IsExpanded = true; }); _border_notifications.StartDoubleAnimation(Border.HeightProperty, TimeSpan.FromSeconds(0.2), ActualHeight * HEIGHT_RATIO, null, null, 1); UpdateExpanded(true); } private void CloseNotifications() { _border_drag.EnsureHeight(); _border_notifications.EnsureHeight(); _border_notifications.StartDoubleAnimation(Border.HeightProperty, TimeSpan.FromSeconds(0.2), CurrentMinHeight, null, 1, null, () => { _border_notifications.BeginAnimation(Border.HeightProperty, null); _border_notifications.Height = CurrentMinHeight; for (int i = 0; i < _items_control.Items.Count; i++) { FrameworkElement element = _items_control.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; var control = element.FindChild(); if (control != null) { control.Height = control.MinHeight; } } }); _border_drag.StartDoubleAnimation(Border.HeightProperty, TimeSpan.FromSeconds(0.2), CurrentMinHeight, null, 1, null, () => { _border_drag.BeginAnimation(Border.HeightProperty, null); _border_drag.Height = double.NaN; }); ThicknessAnimation tAni = new ThicknessAnimation(); tAni.Duration = TimeSpan.FromSeconds(0.3); tAni.To = new Thickness(0); _border_notifications.BeginAnimation(Border.BorderThicknessProperty, tAni); UpdateExpanded(false); IsExpanded = false; Task.Delay(400).ContinueWith((x) => { Dispatcher.BeginInvoke(new Action(() => { _scrollViewer.ScrollToTop(); })); }); } private void GridNotificationMouseUp(object sender, MouseOrTouchEventArgs e) { if (_isMouseDown) { _isMouseDown = false; if (_isMoving) { if (_touchDevice != null) { _border_drag.ReleaseTouchCapture(_touchDevice); _touchDevice = null; } else { _border_drag.ReleaseMouseCapture(); } e.Handled = true; _isMoving = false; if ((e.GetPosition(this).Y > ActualHeight / 2 && _last_manipulation_delta > 0) || _last_manipulation_delta > 20) { OpenNotifications(); } else if ((e.GetPosition(this).Y < ActualHeight / 2 && _last_manipulation_delta < 0) || _last_manipulation_delta < 20) { CloseNotifications(); } } _scrollViewer.DisableScrolling = false; } } private void GridNotificationMouseMove(object sender, MouseOrTouchEventArgs e) { if (_isMouseDown) { if (Math.Abs(e.Location.Y - _downLocation.Y) > 5) { _isMoving = true; } if (_isMoving) { DateTime curTime = DateTime.Now; if (curTime > _last_delta_measure_time.AddMilliseconds(10)) { _last_delta_measure_time = curTime; double delta = e.Location.Y - _last_mouse_y; if (delta != 0) { _last_manipulation_delta = delta; } _last_mouse_y = e.Location.Y; } if (e.TouchDevice != null) { _border_drag.CaptureTouch(e.TouchDevice); } else { Mouse.Capture(_border_drag); } _border_drag.Height = Math.Max(e.Location.Y + _mouseDownLocation.Y, CurrentMinHeight); _border_notifications.Height = Math.Min(_border_drag.Height * HEIGHT_RATIO, ActualHeight * HEIGHT_RATIO); _border_drag.BeginAnimation(Border.HeightProperty, null); _border_notifications.BeginAnimation(Border.HeightProperty, null); _border_notifications.BeginAnimation(Border.BorderThicknessProperty, null); if (_border_notifications.ActualHeight > CurrentMinHeight) { _border_notifications.BorderThickness = new Thickness(0, 0, 0, 20); } else { _border_notifications.BorderThickness = new Thickness(0); } } } } private void GridNotificationMouseDown(object sender, MouseOrTouchEventArgs e) { if (!IsExpanded || e.OriginalSource == _border_drag || e.OriginalSource == _border_notifications) { _mouseDownLocation = new Point(e.Location.X, _border_drag.ActualHeight - e.Location.Y); _downLocation = e.Location; _last_mouse_y = _downLocation.Y; _last_delta_measure_time = DateTime.Now; _last_manipulation_delta = 0; _scrollViewer.DisableScrolling = true; _isMouseDown = true; } } } }