private LoopingSelectorItem CreateAndAddItem(Panel parent, object content) { bool reuse = _temporaryItemsPool != null && _temporaryItemsPool.Count > 0; LoopingSelectorItem wrapper = reuse ? _temporaryItemsPool.Dequeue() : new LoopingSelectorItem(); if (!reuse) { wrapper.ContentTemplate = this.ItemTemplate; wrapper.Width = ItemSize.Width; wrapper.Height = ItemSize.Height; wrapper.Padding = ItemPadding; wrapper.Click += OnWrapperClick; } wrapper.DataContext = wrapper.Content = content; parent.Children.Add(wrapper); // Need to do this before calling ApplyTemplate if (!reuse) { wrapper.ApplyTemplate(); } return(wrapper); }
private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e) { if (Orientation == Orientation.Vertical) { if (_isDragging) { AnimatePanel(_panDuration, _panEase, _dragTarget += e.DeltaManipulation.Translation.Y); e.Handled = true; } else if (Math.Abs(e.CumulativeManipulation.Translation.X) > DragSensitivity) { _isAllowedToDragVertically = false; } else if (_isAllowedToDragVertically && Math.Abs(e.CumulativeManipulation.Translation.Y) > DragSensitivity) { _isDragging = true; _state = State.Dragging; e.Handled = true; _selectedItem = null; if (!IsExpanded) { IsExpanded = true; } _dragTarget = _panningTransform.Y; UpdateItemState(); } } else { if (_isDragging) { AnimatePanel(_panDuration, _panEase, _dragTarget += e.DeltaManipulation.Translation.X); e.Handled = true; } else if (Math.Abs(e.CumulativeManipulation.Translation.Y) > DragSensitivity) { _isAllowedToDragHorizontally = false; } else if (_isAllowedToDragHorizontally && Math.Abs(e.CumulativeManipulation.Translation.X) > DragSensitivity) { _isDragging = true; _state = State.Dragging; e.Handled = true; _selectedItem = null; if (!IsExpanded) { IsExpanded = true; } _dragTarget = _panningTransform.X; UpdateItemState(); } } }
private void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { if (_isDragging) { // See if it was a flick if (e.IsInertial) { _state = State.Flicking; _selectedItem = null; if (!IsExpanded) { IsExpanded = true; } Point velocity; if (Orientation == Orientation.Vertical) { velocity = new Point(0, e.FinalVelocities.LinearVelocity.Y); } else { velocity = new Point(e.FinalVelocities.LinearVelocity.X, 0); } double flickDuration = PhysicsConstants.GetStopTime(velocity, Friction, MaximumSpeed, ParkingSpeed); Point flickEndPoint = PhysicsConstants.GetStopPoint(velocity, Friction, MaximumSpeed, ParkingSpeed); IEasingFunction flickEase = PhysicsConstants.GetEasingFunction(flickDuration, Friction); double to; if (Orientation == Orientation.Vertical) { to = _panningTransform.Y + flickEndPoint.Y; } else { to = _panningTransform.X + flickEndPoint.X; } AnimatePanel(new Duration(TimeSpan.FromSeconds(flickDuration)), flickEase, to); e.Handled = true; _selectedItem = null; UpdateItemState(); } if (_state == State.Dragging) { SelectAndSnapToClosest(); } _state = State.Expanded; } }
private static LoopingSelectorItem GetLastItem(LoopingSelectorItem item, out int count) { count = 0; while (item.Next != null) { ++count; item = item.Next; } return(item); }
private static LoopingSelectorItem GetFirstItem(LoopingSelectorItem item, out int count) { count = 0; while (item.Previous != null) { ++count; item = item.Previous; } return(item); }
private int GetClosestItem() { if (!IsReady) { return(-1); } int count = _itemsPanel.Children.Count; double panelY = _panningTransform.Y; double panelX = _panningTransform.X; double halfHeight = ActualItemHeight / 2; double halfWidth = ActualItemWidth / 2; int found = -1; double closestDistance = double.MaxValue; for (int index = 0; index < count; ++index) { LoopingSelectorItem wrapper = (LoopingSelectorItem)_itemsPanel.Children[index]; double distance; if (Orientation == Orientation.Vertical) { distance = Math.Abs((wrapper.Transform.Y + halfHeight) + panelY); if (distance <= halfHeight) { found = index; break; } } else { distance = Math.Abs((wrapper.Transform.X + halfWidth) + panelX); if (distance <= halfWidth) { found = index; break; } } if (closestDistance > distance) { closestDistance = distance; found = index; } } return(found); }
internal void InsertAfter(LoopingSelectorItem after) { Next = after.Next; Previous = after; if (after.Next != null) { after.Next.Previous = this; } after.Next = this; }
internal void InsertBefore(LoopingSelectorItem before) { Next = before; Previous = before.Previous; if (before.Previous != null) { before.Previous.Next = this; } before.Previous = this; }
internal void Remove() { if (Previous != null) { Previous.Next = Next; } if (Next != null) { Next.Previous = Previous; } Next = Previous = null; }
private void SelectAndSnapTo(LoopingSelectorItem item) { if (item == null) { return; } if (_selectedItem != null) { _selectedItem.SetState(IsExpanded ? LoopingSelectorItem.State.Expanded : LoopingSelectorItem.State.Normal, true); } if (_selectedItem != item) { _selectedItem = item; // Update DataSource.SelectedItem aynchronously so that animations have a chance to start. Dispatcher.BeginInvoke(() => { _isSelecting = true; DataSource.SelectedItem = item.DataContext; _isSelecting = false; }); } _selectedItem.SetState(LoopingSelectorItem.State.Selected, true); TranslateTransform transform = item.Transform; if (transform != null) { if (Orientation == Orientation.Vertical) { double newPosition = -transform.Y - Math.Round(item.ActualHeight / 2); if (_panningTransform.Y != newPosition) { AnimatePanel(_selectDuration, _selectEase, newPosition); } } else { double newPosition = -transform.X - Math.Round(item.ActualWidth / 2); if (_panningTransform.X != newPosition) { AnimatePanel(_selectDuration, _selectEase, newPosition); } } } }
private void SelectAndSnapToClosest() { if (!IsReady) { return; } int index = GetClosestItem(); if (index == -1) { return; } LoopingSelectorItem item = (LoopingSelectorItem)_itemsPanel.Children[index]; SelectAndSnapTo(item); }
private static LoopingSelectorItem GetLastItem(LoopingSelectorItem item, out int count) { count = 0; while (item.Next != null) { ++count; item = item.Next; } return item; }
private static LoopingSelectorItem GetFirstItem(LoopingSelectorItem item, out int count) { count = 0; while (item.Previous != null) { ++count; item = item.Previous; } return item; }
/// <summary> /// Balances the items. /// </summary> private void Balance() { if (!IsReady) { return; } double actualItemWidth = ActualItemWidth; double actualItemHeight = ActualItemHeight; if (Orientation == Orientation.Vertical) _additionalItemsCount = (int) Math.Round((ActualHeight * 1.5) / actualItemHeight); else _additionalItemsCount = (int) Math.Round((ActualWidth * 1.5) / actualItemWidth); LoopingSelectorItem closestToMiddle = null; int closestToMiddleIndex = -1; if (_itemsPanel.Children.Count == 0) { // We need to get the selection and start from there closestToMiddleIndex = 0; _selectedItem = closestToMiddle = CreateAndAddItem(_itemsPanel, DataSource.SelectedItem); if (Orientation == Orientation.Vertical) { closestToMiddle.Transform.Y = -actualItemHeight / 2; closestToMiddle.Transform.X = (ActualWidth - actualItemWidth) / 2; } else { closestToMiddle.Transform.X = -actualItemWidth / 2; closestToMiddle.Transform.Y = (ActualHeight - actualItemHeight) / 2; } closestToMiddle.SetState(LoopingSelectorItem.State.Selected, false); } else { closestToMiddleIndex = GetClosestItem(); closestToMiddle = (LoopingSelectorItem) _itemsPanel.Children[closestToMiddleIndex]; } int itemsBeforeCount; LoopingSelectorItem firstItem = GetFirstItem(closestToMiddle, out itemsBeforeCount); int itemsAfterCount; LoopingSelectorItem lastItem = GetLastItem(closestToMiddle, out itemsAfterCount); // Does the top need items? if (itemsBeforeCount < itemsAfterCount || itemsBeforeCount < _additionalItemsCount) { while (itemsBeforeCount < _additionalItemsCount) { object newData = DataSource.GetPrevious(firstItem.DataContext); if (newData == null) { // There may be room to display more items, but there is no more data. if (Orientation == Orientation.Vertical) _maximumPanelScroll = -firstItem.Transform.Y - actualItemHeight / 2; else _maximumPanelScroll = -firstItem.Transform.X - actualItemWidth / 2; if (_panelAnimation.To != null && (_isAnimating && _panelAnimation.To.Value > _maximumPanelScroll)) { Brake(_maximumPanelScroll); } break; } LoopingSelectorItem newItem = null; // Can an item from the bottom be re-used? if (itemsAfterCount > _additionalItemsCount) { newItem = lastItem; lastItem = lastItem.Previous; newItem.Remove(); newItem.Content = newItem.DataContext = newData; } else { // Make a new item newItem = CreateAndAddItem(_itemsPanel, newData); if (Orientation == Orientation.Vertical) newItem.Transform.X = (ActualWidth - actualItemWidth) / 2; else newItem.Transform.Y = (ActualHeight - actualItemHeight) / 2; } // Put the new item on the top if (Orientation == Orientation.Vertical) newItem.Transform.Y = firstItem.Transform.Y - actualItemHeight; else newItem.Transform.X = firstItem.Transform.X - actualItemWidth; newItem.InsertBefore(firstItem); firstItem = newItem; ++itemsBeforeCount; } } // Does the bottom need items? if (itemsAfterCount < itemsBeforeCount || itemsAfterCount < _additionalItemsCount) { while (itemsAfterCount < _additionalItemsCount) { object newData = DataSource.GetNext(lastItem.DataContext); if (newData == null) { // There may be room to display more items, but there is no more data. if (Orientation == Orientation.Vertical) _minimumPanelScroll = -lastItem.Transform.Y - actualItemHeight / 2; else _minimumPanelScroll = -lastItem.Transform.X - actualItemWidth / 2; if (_panelAnimation.To != null && (_isAnimating && _panelAnimation.To.Value < _minimumPanelScroll)) { Brake(_minimumPanelScroll); } break; } LoopingSelectorItem newItem = null; // Can an item from the top be re-used? if (itemsBeforeCount > _additionalItemsCount) { newItem = firstItem; firstItem = firstItem.Next; newItem.Remove(); newItem.Content = newItem.DataContext = newData; } else { // Make a new item newItem = CreateAndAddItem(_itemsPanel, newData); if (Orientation == Orientation.Vertical) newItem.Transform.X = (ActualWidth - actualItemWidth) / 2; else newItem.Transform.Y = (ActualHeight - ActualItemHeight) / 2; } // Put the new item on the bottom if (Orientation == Orientation.Vertical) newItem.Transform.Y = lastItem.Transform.Y + actualItemHeight; else newItem.Transform.X = lastItem.Transform.X + actualItemWidth; newItem.InsertAfter(lastItem); lastItem = newItem; ++itemsAfterCount; } } _temporaryItemsPool = null; }
private async void SelectAndSnapTo(LoopingSelectorItem item) { if (item == null) { return; } if (_selectedItem != null) { _selectedItem.SetState(IsExpanded ? LoopingSelectorItem.State.Expanded : LoopingSelectorItem.State.Normal, true); } if (_selectedItem != item) { _selectedItem = item; // Update DataSource.SelectedItem aynchronously so that animations have a chance to start. await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { _isSelecting = true; DataSource.SelectedItem = item.DataContext; _isSelecting = false; }); } _selectedItem.SetState(LoopingSelectorItem.State.Selected, true); TranslateTransform transform = item.Transform; if (transform != null) { if (Orientation == Orientation.Vertical) { double newPosition = -transform.Y - Math.Round(item.ActualHeight / 2); if (_panningTransform.Y != newPosition) { AnimatePanel(_selectDuration, _selectEase, newPosition); } } else { double newPosition = -transform.X - Math.Round(item.ActualWidth / 2); if (_panningTransform.X != newPosition) { AnimatePanel(_selectDuration, _selectEase, newPosition); } } } }
private void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) { if (_isDragging) { // See if it was a flick if (e.IsInertial) { _state = State.Flicking; _selectedItem = null; if (!IsExpanded) { IsExpanded = true; } Point velocity; if (Orientation == Orientation.Vertical) velocity = new Point(0, e.Velocities.Linear.Y); else velocity = new Point(e.Velocities.Linear.X, 0); double flickDuration = PhysicsConstants.GetStopTime(velocity, Friction, MaximumSpeed, ParkingSpeed); Point flickEndPoint = PhysicsConstants.GetStopPoint(velocity, Friction, MaximumSpeed, ParkingSpeed); var flickEase = PhysicsConstants.GetEasingFunction(flickDuration, Friction); double to; if (Orientation == Orientation.Vertical) to = _panningTransform.Y + flickEndPoint.Y; else to = _panningTransform.X + flickEndPoint.X; AnimatePanel(new Duration(TimeSpan.FromSeconds(flickDuration)), flickEase, to); e.Handled = true; _selectedItem = null; UpdateItemState(); } if (_state == State.Dragging) { SelectAndSnapToClosest(); } _state = State.Expanded; } }
private void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) { if (Orientation == Orientation.Vertical) { if (_isDragging) { AnimatePanel(_panDuration, _panEase, _dragTarget += e.Delta.Translation.Y); e.Handled = true; } else if (Math.Abs(e.Cumulative.Translation.X) > DragSensitivity) { _isAllowedToDragVertically = false; } else if (_isAllowedToDragVertically && Math.Abs(e.Cumulative.Translation.Y) > DragSensitivity) { _isDragging = true; _state = State.Dragging; e.Handled = true; _selectedItem = null; if (!IsExpanded) { IsExpanded = true; } _dragTarget = _panningTransform.Y; UpdateItemState(); } } else { if (_isDragging) { AnimatePanel(_panDuration, _panEase, _dragTarget += e.Delta.Translation.X); e.Handled = true; } else if (Math.Abs(e.Cumulative.Translation.Y) > DragSensitivity) { _isAllowedToDragHorizontally = false; } else if (_isAllowedToDragHorizontally && Math.Abs(e.Cumulative.Translation.X) > DragSensitivity) { _isDragging = true; _state = State.Dragging; e.Handled = true; _selectedItem = null; if (!IsExpanded) { IsExpanded = true; } _dragTarget = _panningTransform.X; UpdateItemState(); } } }
internal void InsertBefore(LoopingSelectorItem before) { Next = before; Previous = before.Previous; if (before.Previous != null) { before.Previous.Next = this; } before.Previous = this; }
internal void InsertAfter(LoopingSelectorItem after) { Next = after.Next; Previous = after; if (after.Next != null) { after.Next.Previous = this; } after.Next = this; }
internal void Remove() { if (Previous != null) { Previous.Next = Next; } if (Next != null) { Next.Previous = Previous; } Next = Previous = null; }
/// <summary> /// Balances the items. /// </summary> private void Balance() { if (!IsReady) { return; } double actualItemWidth = ActualItemWidth; double actualItemHeight = ActualItemHeight; if (Orientation == Orientation.Vertical) { _additionalItemsCount = (int)Math.Round((ActualHeight * 1.5) / actualItemHeight); } else { _additionalItemsCount = (int)Math.Round((ActualWidth * 1.5) / actualItemWidth); } LoopingSelectorItem closestToMiddle = null; int closestToMiddleIndex = -1; if (_itemsPanel.Children.Count == 0) { // We need to get the selection and start from there closestToMiddleIndex = 0; _selectedItem = closestToMiddle = CreateAndAddItem(_itemsPanel, DataSource.SelectedItem); if (Orientation == Orientation.Vertical) { closestToMiddle.Transform.Y = -actualItemHeight / 2; closestToMiddle.Transform.X = (ActualWidth - actualItemWidth) / 2; } else { closestToMiddle.Transform.X = -actualItemWidth / 2; closestToMiddle.Transform.Y = (ActualHeight - actualItemHeight) / 2; } closestToMiddle.SetState(LoopingSelectorItem.State.Selected, false); } else { closestToMiddleIndex = GetClosestItem(); closestToMiddle = (LoopingSelectorItem)_itemsPanel.Children[closestToMiddleIndex]; } int itemsBeforeCount; LoopingSelectorItem firstItem = GetFirstItem(closestToMiddle, out itemsBeforeCount); int itemsAfterCount; LoopingSelectorItem lastItem = GetLastItem(closestToMiddle, out itemsAfterCount); // Does the top need items? if (itemsBeforeCount < itemsAfterCount || itemsBeforeCount < _additionalItemsCount) { while (itemsBeforeCount < _additionalItemsCount) { object newData = DataSource.GetPrevious(firstItem.DataContext); if (newData == null) { // There may be room to display more items, but there is no more data. if (Orientation == Orientation.Vertical) { _maximumPanelScroll = -firstItem.Transform.Y - actualItemHeight / 2; } else { _maximumPanelScroll = -firstItem.Transform.X - actualItemWidth / 2; } if (_panelAnimation.To != null && (_isAnimating && _panelAnimation.To.Value > _maximumPanelScroll)) { Brake(_maximumPanelScroll); } break; } LoopingSelectorItem newItem = null; // Can an item from the bottom be re-used? if (itemsAfterCount > _additionalItemsCount) { newItem = lastItem; lastItem = lastItem.Previous; newItem.Remove(); newItem.Content = newItem.DataContext = newData; } else { // Make a new item newItem = CreateAndAddItem(_itemsPanel, newData); if (Orientation == Orientation.Vertical) { newItem.Transform.X = (ActualWidth - actualItemWidth) / 2; } else { newItem.Transform.Y = (ActualHeight - actualItemHeight) / 2; } } // Put the new item on the top if (Orientation == Orientation.Vertical) { newItem.Transform.Y = firstItem.Transform.Y - actualItemHeight; } else { newItem.Transform.X = firstItem.Transform.X - actualItemWidth; } newItem.InsertBefore(firstItem); firstItem = newItem; ++itemsBeforeCount; } } // Does the bottom need items? if (itemsAfterCount < itemsBeforeCount || itemsAfterCount < _additionalItemsCount) { while (itemsAfterCount < _additionalItemsCount) { object newData = DataSource.GetNext(lastItem.DataContext); if (newData == null) { // There may be room to display more items, but there is no more data. if (Orientation == Orientation.Vertical) { _minimumPanelScroll = -lastItem.Transform.Y - actualItemHeight / 2; } else { _minimumPanelScroll = -lastItem.Transform.X - actualItemWidth / 2; } if (_panelAnimation.To != null && (_isAnimating && _panelAnimation.To.Value < _minimumPanelScroll)) { Brake(_minimumPanelScroll); } break; } LoopingSelectorItem newItem = null; // Can an item from the top be re-used? if (itemsBeforeCount > _additionalItemsCount) { newItem = firstItem; firstItem = firstItem.Next; newItem.Remove(); newItem.Content = newItem.DataContext = newData; } else { // Make a new item newItem = CreateAndAddItem(_itemsPanel, newData); if (Orientation == Orientation.Vertical) { newItem.Transform.X = (ActualWidth - actualItemWidth) / 2; } else { newItem.Transform.Y = (ActualHeight - ActualItemHeight) / 2; } } // Put the new item on the bottom if (Orientation == Orientation.Vertical) { newItem.Transform.Y = lastItem.Transform.Y + actualItemHeight; } else { newItem.Transform.X = lastItem.Transform.X + actualItemWidth; } newItem.InsertAfter(lastItem); lastItem = newItem; ++itemsAfterCount; } } _temporaryItemsPool = null; }