1b38b39ae0afdeb9ab0df20f3d1859027c8d13d7
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Picker.cs
1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using Tizen.NUI;
18 using Tizen.NUI.BaseComponents;
19 using System.Collections.Generic;
20 using System.Collections.ObjectModel;
21 using System.ComponentModel;
22 using System.Diagnostics.CodeAnalysis;
23
24 namespace Tizen.NUI.Components
25 {
26     /// <summary>
27     /// ValueChangedEventArgs is a class to notify changed Picker value argument which will sent to user.
28     /// </summary>
29     /// <since_tizen> 9 </since_tizen>
30     public class ValueChangedEventArgs : EventArgs
31     {
32         /// <summary>
33         /// ValueChangedEventArgs default constructor.
34         /// <param name="value">value of Picker.</param>
35         /// </summary>
36         [EditorBrowsable(EditorBrowsableState.Never)]
37         public ValueChangedEventArgs(int value)
38         {
39             Value = value;
40         }
41
42         /// <summary>
43         /// ValueChangedEventArgs default constructor.
44         /// <returns>The current value of Picker.</returns>
45         /// </summary>
46         /// <since_tizen> 9 </since_tizen>
47         public int Value { get; }
48
49     }
50
51     /// <summary>
52     /// Picker is a class which provides a function that allows the user to select 
53     /// a value through a scrolling motion by expressing the specified value as a list.
54     /// </summary>
55     /// <since_tizen> 9 </since_tizen>
56     public partial class Picker : Control
57     {
58         //Tizen 6.5 base components Picker guide visible scroll item is 5.
59         private const int scrollVisibleItems = 5;
60         //Dummy item count for loop feature. Max value of scrolling distance in 
61         //RPI target is bigger than 20 items height. it can adjust depends on the internal logic and device env.
62         private const int dummyItemsForLoop = 20;
63         private int startScrollOffset;
64         private int itemHeight;
65         private int startScrollY;
66         private int startY;
67         private int pageSize;
68         private int currentValue;
69         private int maxValue;
70         private int minValue;
71         private int lastScrollPosion;
72         private bool onAnimation; //Scroller on animation check.
73         private bool onAlignAnimation;
74         private bool displayedValuesUpdate; //User sets displayed value check.
75         private bool needItemUpdate; //min, max or display value updated check.
76         private bool loopEnabled;
77         private ReadOnlyCollection<string> displayedValues;
78         private PickerScroller pickerScroller;
79         private View upLine;
80         private View downLine;
81         private IList<TextLabel> itemList;
82         private Vector2 size;
83         private TextLabelStyle itemTextLabel;
84         private bool editMode = false;
85         private View recoverIndicator = null;
86         private View editModeIndicator = null;
87
88
89         /// <summary>
90         /// Creates a new instance of Picker.
91         /// </summary>
92         /// <since_tizen> 9 </since_tizen>
93         public Picker()
94         {
95         }
96
97         /// <summary>
98         /// Creates a new instance of Picker.
99         /// </summary>
100         /// <param name="style">Creates Picker by special style defined in UX.</param>
101         /// <since_tizen> 9 </since_tizen>
102         public Picker(string style) : base(style)
103         {
104         }
105
106         /// <summary>
107         /// Creates a new instance of Picker.
108         /// </summary>
109         /// <param name="pickerStyle">Creates Picker by style customized by user.</param>
110         /// <since_tizen> 9 </since_tizen>
111         public Picker(PickerStyle pickerStyle) : base(pickerStyle)
112         {
113         }
114
115         /// <summary>
116         /// Dispose Picker and all children on it.
117         /// </summary>
118         /// <param name="type">Dispose type.</param>
119         [EditorBrowsable(EditorBrowsableState.Never)]
120         protected override void Dispose(DisposeTypes type)
121         {
122             if (disposed)
123             {
124                 return;
125             }
126
127             if (type == DisposeTypes.Explicit)
128             {
129                 if (itemList != null)
130                 {
131                     foreach (TextLabel textLabel in itemList)
132                     {
133                         if (pickerScroller) pickerScroller.Remove(textLabel);
134                         Utility.Dispose(textLabel);
135                     }
136
137                     itemList = null;
138                 }
139
140                 if (pickerScroller != null)
141                 {
142                     Remove(pickerScroller);
143                     Utility.Dispose(pickerScroller);
144                     pickerScroller = null;
145                 }
146
147                 Remove(upLine);
148                 Utility.Dispose(upLine);
149                 Remove(downLine);
150                 Utility.Dispose(downLine);
151
152                 recoverIndicator = null;
153                 if (editModeIndicator)
154                 {
155                     editModeIndicator.Dispose();
156                     editModeIndicator = null;
157                 }
158             }
159
160             base.Dispose(type);
161         }
162
163         /// <summary>
164         /// An event emitted when Picker value changed, user can subscribe or unsubscribe to this event handler.
165         /// </summary>
166         /// <since_tizen> 9 </since_tizen>
167         public event EventHandler<ValueChangedEventArgs> ValueChanged;
168
169         //TODO Fomatter here
170
171         /// <summary>
172         /// The values to be displayed instead of numbers.
173         /// </summary>
174         /// <since_tizen> 9 </since_tizen>
175         public ReadOnlyCollection<String> DisplayedValues
176         {
177             get
178             {
179                 return displayedValues;
180             }
181             set
182             {
183                 displayedValues = value;
184
185                 needItemUpdate = true;
186                 displayedValuesUpdate = true;
187
188                 UpdateValueList();
189             }
190         }
191
192         /// <summary>
193         /// The Current value of Picker.
194         /// </summary>
195         /// <since_tizen> 9 </since_tizen>
196         public int CurrentValue
197         {
198             get
199             {
200                 return (int)GetValue(CurrentValueProperty);
201             }
202             set
203             {
204                 SetValue(CurrentValueProperty, value);
205                 NotifyPropertyChanged();
206             }
207         }
208         private int InternalCurrentValue
209         {
210             get
211             {
212                 return currentValue;
213             }
214             set
215             {
216                 if (currentValue == value) return;
217
218                 if (currentValue < minValue) currentValue = minValue;
219                 else if (currentValue > maxValue) currentValue = maxValue;
220
221                 currentValue = value;
222
223                 UpdateCurrentValue();
224             }
225         }
226
227         /// <summary>
228         /// The max value of Picker.
229         /// </summary>
230         /// <since_tizen> 9 </since_tizen>
231         public int MaxValue
232         {
233             get
234             {
235                 return (int)GetValue(MaxValueProperty);
236             }
237             set
238             {
239                 SetValue(MaxValueProperty, value);
240                 NotifyPropertyChanged();
241             }
242         }
243         private int InternalMaxValue
244         {
245             get
246             {
247                 return maxValue;
248             }
249             set
250             {
251                 if (maxValue == value) return;
252                 if (currentValue > value) currentValue = value;
253
254                 maxValue = value;
255                 needItemUpdate = true;
256
257                 UpdateValueList();
258             }
259         }
260
261         /// <summary>
262         /// The min value of Picker.
263         /// </summary>
264         /// <since_tizen> 9 </since_tizen>
265         public int MinValue
266         {
267             get
268             {
269                 return (int)GetValue(MinValueProperty);
270             }
271             set
272             {
273                 SetValue(MinValueProperty, value);
274                 NotifyPropertyChanged();
275             }
276         }
277         private int InternalMinValue
278         {
279             get
280             {
281                 return minValue;
282             }
283             set
284             {
285                 if (minValue == value) return;
286                 if (currentValue < value) currentValue = value;
287
288                 minValue = value;
289                 needItemUpdate = true;
290
291                 UpdateValueList();
292             }
293         }
294
295         /// <inheritdoc/>
296         [EditorBrowsable(EditorBrowsableState.Never)]
297         public override void OnInitialize()
298         {
299             base.OnInitialize();
300             AccessibilityRole = Role.List;
301
302             Initialize();
303         }
304
305         /// <summary>
306         /// Applies style to Picker.
307         /// </summary>
308         /// <param name="viewStyle">The style to apply.</param>
309         [EditorBrowsable(EditorBrowsableState.Never)]
310         public override void ApplyStyle(ViewStyle viewStyle)
311         {
312             base.ApplyStyle(viewStyle);
313
314             var pickerStyle = viewStyle as PickerStyle;
315
316             if (pickerStyle == null) return;
317
318             pickerScroller?.SetPickerStyle(pickerStyle);
319
320             //Apply StartScrollOffset style.
321             if (pickerStyle.StartScrollOffset != null)
322             {
323                 startScrollOffset = (int)pickerStyle.StartScrollOffset.Height;
324             }
325
326             //Apply ItemTextLabel style.
327             if (pickerStyle.ItemTextLabel != null)
328             {
329                 if (itemTextLabel == null)
330                 {
331                     itemTextLabel = (TextLabelStyle)pickerStyle.ItemTextLabel.Clone();
332                 }
333                 else
334                 {
335                     itemTextLabel.MergeDirectly(pickerStyle.ItemTextLabel);
336                 }
337
338                 itemHeight = (int)(pickerStyle.ItemTextLabel.Size?.Height ?? 0);
339
340                 if (itemList != null)
341                     foreach (TextLabel textLabel in itemList)
342                         textLabel.ApplyStyle(pickerStyle.ItemTextLabel);
343             }
344
345             //Apply PickerCenterLine style.
346             if (pickerStyle.Divider != null && upLine != null && downLine != null)
347             {
348                 upLine.ApplyStyle(pickerStyle.Divider);
349                 downLine.ApplyStyle(pickerStyle.Divider);
350                 downLine.PositionY = (int)pickerStyle.Divider.PositionY + itemHeight;
351             }
352
353             startScrollY = (itemHeight * dummyItemsForLoop) + startScrollOffset;
354             startY = startScrollOffset;
355         }
356
357         /// <inheritdoc/>
358         [EditorBrowsable(EditorBrowsableState.Never)]
359         public override void OnRelayout(Vector2 size, RelayoutContainer container)
360         {
361             if (size == null) return;
362
363             if (size.Equals(this.size))
364             {
365                 return;
366             }
367
368             this.size = new Vector2(size);
369
370             if (pickerScroller != null && itemList != null)
371             {
372                 pickerScroller.ScrollAvailableArea = new Vector2(0, (itemList.Count * itemHeight) - size.Height);
373             }
374         }
375
376         private void Initialize()
377         {
378             HeightSpecification = LayoutParamPolicies.MatchParent;
379
380             //Picker Using scroller internally. actually it is a kind of scroller which has infinity loop,
381             //and item center align features.
382             pickerScroller = new PickerScroller()
383             {
384                 WidthSpecification = LayoutParamPolicies.MatchParent,
385                 HeightSpecification = LayoutParamPolicies.MatchParent,
386                 ScrollingDirection = ScrollableBase.Direction.Vertical,
387                 Layout = new LinearLayout()
388                 {
389                     LinearOrientation = LinearLayout.Orientation.Vertical,
390                 },
391                 //FIXME: Need to expand as many as possible;
392                 //       When user want to start list middle of the list item. currently confused how to create list before render.
393                 ScrollAvailableArea = new Vector2(0, 10000),
394                 Name = "pickerScroller",
395             };
396
397             pickerScroller.Scrolling += OnScroll;
398             pickerScroller.ScrollAnimationEnded += OnScrollAnimationEnded;
399             pickerScroller.ScrollAnimationStarted += OnScrollAnimationStarted;
400
401             itemList = new List<TextLabel>();
402
403             minValue = maxValue = currentValue = 0;
404             displayedValues = null;
405             //Those many flags for min, max, value method calling sequence dependency.
406             needItemUpdate = true;
407             displayedValuesUpdate = false;
408             onAnimation = false;
409             loopEnabled = false;
410
411             Add(pickerScroller);
412             AddLine();
413
414             Focusable = true;
415             KeyEvent += OnKeyEvent;
416         }
417
418         private void OnValueChanged()
419         {
420             ValueChangedEventArgs eventArgs =
421                 new ValueChangedEventArgs(displayedValuesUpdate ? Int32.Parse(itemList[currentValue].Name) : Int32.Parse(itemList[currentValue].Text));
422             ValueChanged?.Invoke(this, eventArgs);
423         }
424
425         private void PageAdjust(float positionY)
426         {
427             //Check the scroll is going out to the dummys if so, bring it back to page.
428             if (positionY > -(startScrollY - (itemHeight * 2)))
429                 pickerScroller.ScrollTo(-positionY + pageSize, false);
430             else if (positionY < -(startScrollY + pageSize - (itemHeight * 2)))
431                 pickerScroller.ScrollTo(-positionY - pageSize, false);
432         }
433
434         private void OnScroll(object sender, ScrollEventArgs e)
435         {
436             if (!loopEnabled || onAnimation || onAlignAnimation) return;
437
438             PageAdjust(e.Position.Y);
439         }
440
441         private void OnScrollAnimationStarted(object sender, ScrollEventArgs e)
442         {
443             onAnimation = true;
444         }
445
446         private void OnScrollAnimationEnded(object sender, ScrollEventArgs e)
447         {
448             //Ignore if the scroll position was not changed. (called it from this function)
449             if (lastScrollPosion == (int)e.Position.Y) return;
450
451             //Calc offset from closest item.
452             int offset = (int)(e.Position.Y + startScrollOffset) % itemHeight;
453             if (offset < -(itemHeight / 2)) offset += itemHeight;
454
455             lastScrollPosion = (int)(-e.Position.Y + offset);
456
457             onAnimation = false;
458             if (onAlignAnimation)
459             {
460                 onAlignAnimation = false;
461                 if (loopEnabled == true)
462                 {
463                     PageAdjust(e.Position.Y);
464                 }
465                 if (currentValue != ((int)(-e.Position.Y / itemHeight) + 2))
466                 {
467                     currentValue = ((int)(-e.Position.Y / itemHeight) + 2);
468                     OnValueChanged();
469                 }
470
471                 return;
472             }
473
474             //Item center align with animation, otherwise changed event emit.
475             if (offset != 0)
476             {
477                 onAlignAnimation = true;
478                 pickerScroller.ScrollTo(-e.Position.Y + offset, true);
479             }
480             else
481             {
482                 if (currentValue != ((int)(-e.Position.Y / itemHeight) + 2))
483                 {
484                     currentValue = ((int)(-e.Position.Y / itemHeight) + 2);
485                     OnValueChanged();
486                 }
487             }
488         }
489
490         //This is UI requirement. It helps where exactly center item is.
491         private void AddLine()
492         {
493             upLine = new View();
494             downLine = new View();
495
496             Add(upLine);
497             Add(downLine);
498         }
499
500         private String GetItemText(bool loopEnabled, int idx)
501         {
502             if (!loopEnabled) return " ";
503             else
504             {
505                 if (displayedValuesUpdate)
506                 {
507                     idx = idx - MinValue;
508                     if (idx <= displayedValues.Count)
509                     {
510                         return displayedValues[idx];
511                     }
512                     return " ";
513                 }
514
515                 return idx.ToString();
516             }
517         }
518
519         //FIXME: If textVisual can add in scroller please change it to textVisual for performance
520         [SuppressMessage("Microsoft.Reliability",
521                          "CA2000:DisposeObjectsBeforeLosingScope",
522                          Justification = "The items are added to itemList and are disposed in Picker.Dispose().")]
523         private void AddPickerItem(bool loopEnabled, int idx)
524         {
525             TextLabel temp = new TextLabel(itemTextLabel)
526             {
527                 WidthSpecification = LayoutParamPolicies.MatchParent,
528                 Text = GetItemText(loopEnabled, idx),
529                 Name = idx.ToString(),
530             };
531
532             temp.AccessibilitySuppressedEvents[AccessibilityEvent.MovedOut] = true;
533             itemList.Add(temp);
534             pickerScroller.Add(temp);
535         }
536
537         private void UpdateCurrentValue()
538         {
539             // -2 for center align
540             int startItemIdx = (currentValue == 0) ? -2 : currentValue - minValue - 2;
541
542             if (loopEnabled) startY = ((dummyItemsForLoop + startItemIdx) * itemHeight) + startScrollOffset;
543             // + 2 for non loop picker center align
544             else
545             {
546                 startY = ((2 + startItemIdx) * itemHeight) + startScrollOffset;
547                 currentValue = currentValue - minValue + 2;
548             }
549             pickerScroller.ScrollTo(startY, false);
550         }
551
552         private void UpdateValueList()
553         {
554             if (!needItemUpdate) return;
555             if (minValue > maxValue) return;
556
557             //FIXME: This is wrong.
558             //       But scroller can't update item property after added please fix me.
559             if (itemList.Count > 0)
560             {
561                 itemList.Clear();
562                 pickerScroller.RemoveAllChildren();
563             }
564
565             if (maxValue - minValue + 1 >= scrollVisibleItems)
566             {
567                 loopEnabled = true;
568                 //Current scroller can't add at specific index.
569                 //So need below calc.
570                 int dummyStartIdx = 0;
571                 if (maxValue - minValue >= dummyItemsForLoop)
572                     dummyStartIdx = maxValue - dummyItemsForLoop + 1;
573                 else
574                     dummyStartIdx = maxValue - (dummyItemsForLoop % (maxValue - minValue + 1)) + 1;
575
576                 //Start add items in scroller. first dummys for scroll anim.
577                 for (int i = 0; i < dummyItemsForLoop; i++)
578                 {
579                     if (dummyStartIdx > maxValue) dummyStartIdx = minValue;
580                     AddPickerItem(loopEnabled, dummyStartIdx++);
581                 }
582                 //Second real items.
583                 for (int i = minValue; i <= maxValue; i++)
584                 {
585                     AddPickerItem(loopEnabled, i);
586                 }
587                 //Last dummys for scroll anim.
588                 dummyStartIdx = minValue;
589                 for (int i = 0; i < dummyItemsForLoop; i++)
590                 {
591                     if (dummyStartIdx > maxValue) dummyStartIdx = minValue;
592                     AddPickerItem(loopEnabled, dummyStartIdx++);
593                 }
594             }
595             else
596             {
597                 loopEnabled = false;
598
599                 for (int i = 0; i < 2; i++)
600                     AddPickerItem(loopEnabled, 0);
601                 for (int i = minValue; i <= maxValue; i++)
602                     AddPickerItem(!loopEnabled, i);
603                 for (int i = 0; i < 2; i++)
604                     AddPickerItem(loopEnabled, 0);
605
606             }
607             pageSize = itemHeight * (maxValue - minValue + 1);
608
609             UpdateCurrentValue();
610
611             //Give a correct scroll area.
612             if (size != null)
613             {
614                 pickerScroller.ScrollAvailableArea = new Vector2(0, (itemList.Count * itemHeight) - size.Height);
615             }
616
617             needItemUpdate = false;
618         }
619
620         private bool OnKeyEvent(object o, View.KeyEventArgs e)
621         {
622             if (e.Key.State == Key.StateType.Down)
623             {
624                 if (e.Key.KeyPressedName == "Return")
625                 {
626                     if (editMode)
627                     {
628                         //Todo: sometimes this gets wrong. the currentValue is not correct. need to be fixed.
629                         if (currentValue != ((int)(-pickerScroller.Position.Y / itemHeight) + 2))
630                         {
631                             currentValue = ((int)(-pickerScroller.Position.Y / itemHeight) + 2);
632                             OnValueChanged();
633                         }
634
635                         //set editMode false (toggle the mode)
636                         editMode = false;
637                         FocusManager.Instance.FocusIndicator = recoverIndicator;
638                         return true;
639                     }
640                     else
641                     {
642                         //set editMode true (toggle the mode)
643                         editMode = true;
644                         if (editModeIndicator == null)
645                         {
646                             editModeIndicator = new View()
647                             {
648                                 PositionUsesPivotPoint = true,
649                                 PivotPoint = new Position(0, 0, 0),
650                                 WidthResizePolicy = ResizePolicyType.FillToParent,
651                                 HeightResizePolicy = ResizePolicyType.FillToParent,
652                                 BorderlineColor = Color.Red,
653                                 BorderlineWidth = 6.0f,
654                                 BorderlineOffset = -1f,
655                                 BackgroundColor = new Color(0.2f, 0.2f, 0.2f, 0.4f),
656                             };
657                         }
658                         recoverIndicator = FocusManager.Instance.FocusIndicator;
659                         FocusManager.Instance.FocusIndicator = editModeIndicator;
660                         return true;
661                     }
662                 }
663                 else if (e.Key.KeyPressedName == "Up")
664                 {
665                     if (editMode)
666                     {
667                         InternalCurrentValue += 1;
668                         return true;
669                     }
670                 }
671                 else if (e.Key.KeyPressedName == "Down")
672                 {
673                     if (editMode)
674                     {
675                         InternalCurrentValue -= 1;
676                         return true;
677                     }
678                 }
679
680                 if (editMode)
681                 {
682                     return true;
683                 }
684             }
685             return false;
686         }
687
688
689         internal class PickerScroller : ScrollableBase
690         {
691             private int itemHeight;
692             private int startScrollOffset;
693             private float velocityOfLastPan = 0.0f;
694             private float panAnimationDuration = 0.0f;
695             private float panAnimationDelta = 0.0f;
696             private float decelerationRate = 0.0f;
697             private float logValueOfDeceleration = 0.0f;
698             private delegate float UserAlphaFunctionDelegate(float progress);
699             private UserAlphaFunctionDelegate customScrollAlphaFunction;
700
701             public PickerScroller() : base()
702             {
703                 //Default rate is 0.998. this is for reduce scroll animation length.
704                 decelerationRate = 0.991f;
705                 logValueOfDeceleration = (float)Math.Log(decelerationRate);
706             }
707
708             public void SetPickerStyle(PickerStyle pickerStyle)
709             {
710                 if (pickerStyle.StartScrollOffset != null)
711                 {
712                     startScrollOffset = (int)pickerStyle.StartScrollOffset.Height;
713                 }
714
715                 if (pickerStyle.ItemTextLabel?.Size != null)
716                 {
717                     itemHeight = (int)pickerStyle.ItemTextLabel.Size.Height;
718                 }
719
720                 if (pickerStyle.Size != null)
721                 {
722                     Size = new Size(-1, pickerStyle.Size.Height);
723                 }
724             }
725
726             private float CustomScrollAlphaFunction(float progress)
727             {
728                 if (panAnimationDelta == 0)
729                 {
730                     return 1.0f;
731                 }
732                 else
733                 {
734                     // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
735                     // Can get real distance using equation of deceleration (check Decelerating function)
736                     // After get real distance, normalize it
737                     float realDuration = progress * panAnimationDuration;
738                     float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
739                     float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
740
741                     return result;
742                 }
743             }
744
745             //Override Decelerating for Picker feature.
746             protected override void Decelerating(float velocity, Animation animation)
747             {
748                 //Reduce Scroll animation speed.
749                 //The picker is to select items in the scroll area, it is not correct to animate
750                 //the scroll with very high speed.
751                 velocity *= 0.5f;
752                 velocityOfLastPan = Math.Abs(velocity);
753
754                 float currentScrollPosition = -ContentContainer.PositionY;
755                 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
756                 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
757
758                 float destination = -(panAnimationDelta + currentScrollPosition);
759                 //Animation destination has to center of the item.
760                 float align = destination % itemHeight;
761                 destination -= align;
762                 destination -= startScrollOffset;
763
764                 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
765
766                 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : 0;
767                 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
768
769                 if (destination < -maxPosition || destination > minPosition)
770                 {
771                     panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
772                     destination = velocity > 0 ? minPosition : -maxPosition;
773                     destination = -maxPosition + itemHeight;
774
775                     if (panAnimationDelta == 0)
776                     {
777                         panAnimationDuration = 0.0f;
778                     }
779                     else
780                     {
781                         panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
782                     }
783                 }
784                 else
785                 {
786                     panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
787
788                     if (adjustDestination != destination)
789                     {
790                         destination = adjustDestination;
791                         panAnimationDelta = destination + currentScrollPosition;
792                         velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
793                         panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
794                     }
795                 }
796
797                 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
798                 animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
799                 animation.Duration = (int)panAnimationDuration;
800                 animation.AnimateTo(ContentContainer, "PositionY", (int)destination);
801                 animation.Play();
802             }
803         }
804     }
805 }