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 const double DRAGPATH_HEIGH = 20.0; 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; #region Dep_Properties 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)); #endregion #region constructors static TouchNotificationBar() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TouchNotificationBar), new FrameworkPropertyMetadata(typeof(TouchNotificationBar))); } public TouchNotificationBar() { IsExpanded = false; Loaded += TouchNotificationBar_Loaded; NotificationPressedCommand = new RelayCommand(() => { if (IsExpanded && !_scrollViewer.IsAfterScrolling) { CloseNotifications(); } }); } #endregion #region Override_Base 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_drag.RegisterForPreviewMouseOrTouchDown(GridNotificationMouseDown); _border_drag.RegisterForMouseOrTouchMove(GridNotificationMouseMove); _border_drag.RegisterForPreviewMouseOrTouchUp(GridNotificationMouseUp); _border_drag.SizeChanged += Drag_Border_SizeChanged; } #endregion private FrameworkElement GetLastNotification() { if (_items_control == null) return null; return _items_control.ItemContainerGenerator.ContainerFromIndex(0) as FrameworkElement; } private void TouchNotificationBar_Loaded(object sender, RoutedEventArgs e) { _border_drag.MaxHeight = ActualHeight; } private void Drag_Border_SizeChanged(object sender, SizeChangedEventArgs e) { _grid_mask.Opacity = _border_drag.ActualHeight / ActualHeight; if (e.NewSize.Height > CurrentMinHeight && CurrentMinHeight > 0) { _notification_counter_grid.Visibility = Visibility.Hidden; } else if (Notifications.Count > 1 && CurrentMinHeight > 0) { _notification_counter_grid.Visibility = Visibility.Visible; } } /// /// Called when has notifications items or all items removed /// private void OnHasNotificationsChanged() { if (HasNotifications) { UpdateMinHeightByDispalyedItem(); } else { CurrentMinHeight = 0; if (IsExpanded) { CloseNotifications(); } _border_notifications.Height = CurrentMinHeight; _grid_container.MinHeight = 0; _grid_container.Height = 0; } } private async void UpdateMinHeightByDispalyedItem() { var last = GetLastNotification(); if (last != null) { last.RegisterForLoadedOrNow((x, y) => { CurrentMinHeight = last.ActualHeight; _border_notifications.Height = last.ActualHeight; _grid_container.MinHeight = last.ActualHeight; _grid_container.Height = CurrentMinHeight; }); } else if (Notifications.Count > 0) { await Task.Delay(500); UpdateMinHeightByDispalyedItem(); } } /// /// Callback function on Notifications property /// private void OnNotificationsChanged() { if (Notifications is INotifyCollectionChanged) { (Notifications as INotifyCollectionChanged).CollectionChanged += (_, e) => { this.BeginInvoke(() => { if (Notifications.Count > 0) { UpdateMinHeightByDispalyedItem(); } if (IsExpanded == false && Notifications.Count > 1) { _notification_counter_grid.Visibility = Visibility.Visible; } else { _notification_counter_grid.Visibility = Visibility.Hidden; } }); }; } } #region Mouse handlers private void OpenNotifications() { _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); } private void CloseNotifications() { _border_notifications.StartDoubleAnimation(Border.HeightProperty, TimeSpan.FromSeconds(0.2), CurrentMinHeight, null, 1, null, () => { _border_notifications.BeginAnimation(Border.HeightProperty, null); _border_notifications.Height = CurrentMinHeight; }); _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); 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, DRAGPATH_HEIGH); } 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; } } #endregion } }