Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ScrollViewer.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Diagnostics;
6 
7 using SiliconStudio.Core;
8 using SiliconStudio.Core.Mathematics;
9 using SiliconStudio.Paradox.Games;
10 
11 namespace SiliconStudio.Paradox.UI.Controls
12 {
13  /// <summary>
14  /// Represents a scroll viewer.
15  /// A scroll viewer element has an infinite virtual size defined by its <see cref="ScrollingMode"/>.
16  /// The user can move in that virtual size by touching and panning on the screen.
17  /// </summary>
18  [DebuggerDisplay("ScrollViewer - Name={Name}")]
20  {
21  private readonly static Dictionary<ScrollingMode, int[]> scrollModeToDirectionIndices = new Dictionary<ScrollingMode,int[]>
22  {
23  { ScrollingMode.None, new int[0] },
24  { ScrollingMode.Horizontal, new[] { 0 }},
25  { ScrollingMode.Vertical, new[] { 1 }},
26  { ScrollingMode.InDepth, new[] { 2 }},
27  { ScrollingMode.HorizontalVertical, new[] { 0, 1 }},
28  { ScrollingMode.VerticalInDepth, new[] { 1, 2 }},
29  { ScrollingMode.InDepthHorizontal, new[] { 2, 0 }},
30  };
31 
32  private static readonly HashSet<ScrollingMode>[] orientationToSupportedScrollingModes =
33  {
34  new HashSet<ScrollingMode> { ScrollingMode.Horizontal, ScrollingMode.HorizontalVertical, ScrollingMode.InDepthHorizontal},
35  new HashSet<ScrollingMode> { ScrollingMode.HorizontalVertical, ScrollingMode.Vertical, ScrollingMode.VerticalInDepth},
36  new HashSet<ScrollingMode> { ScrollingMode.VerticalInDepth, ScrollingMode.InDepthHorizontal, ScrollingMode.InDepth}
37  };
38 
39  private static Color transparent = new Color(0,0,0,0);
40 
41  private const float ScrollBarHidingSpeed = 1f;
42 
43  /// <summary>
44  /// The key to the ScrollMode dependency property.
45  /// </summary>
46  public readonly static PropertyKey<ScrollingMode> ScrollModePropertyKey = new PropertyKey<ScrollingMode>("ScrollModeKey", typeof(ScrollViewer), DefaultValueMetadata.Static(ScrollingMode.Horizontal), ObjectInvalidationMetadata.New<ScrollingMode>(InvalidateScrollMode));
47 
48  /// <summary>
49  /// The key to the Deceleration dependency property.
50  /// </summary>
51  public readonly static PropertyKey<float> DecelerationPropertyKey = new PropertyKey<float>("DecelerationKey", typeof(ScrollViewer), DefaultValueMetadata.Static(1500f), ValidateValueMetadata.New<float>(ValidateDecelarationProperty));
52 
53  /// <summary>
54  /// The key to the TouchScrollingEnabled dependency property.
55  /// </summary>
56  protected readonly static PropertyKey<bool> TouchScrollingEnabledPropertyKey = new PropertyKey<bool>("TouchScrollingEnabledKey", typeof(ScrollViewer), DefaultValueMetadata.Static(true), ObjectInvalidationMetadata.New<bool>(TouchScrollingEnabledInvalidationCallback));
57 
58  /// <summary>
59  /// The key to the ScrollBarColor dependency property.
60  /// </summary>
61  protected readonly static PropertyKey<Color> ScrollBarColorPropertyKey = new PropertyKey<Color>("ScrollBarColorKey", typeof(ScrollViewer), DefaultValueMetadata.Static(1f * new Color(0.1f, 0.1f, 0.1f, 1f)));
62 
63  /// <summary>
64  /// The key to the ScrollBarThickness dependency property.
65  /// </summary>
66  protected readonly static PropertyKey<float> ScrollBarThicknessPropertyKey = new PropertyKey<float>("ScrollBarThicknessKey", typeof(ScrollViewer), DefaultValueMetadata.Static(6f));
67 
68  /// <summary>
69  /// The current offsets (in virtual pixels) generated by the scrolling on the <see cref="ContentControl.Content"/> element.
70  /// </summary>
72 
73  /// <summary>
74  /// The current speed of the scrolling in virtual pixels.
75  /// </summary>
77 
78  /// <summary>
79  /// Indicate if the user is currently touching the scroll viewer and performing a scroll gesture with its finger.
80  /// </summary>
81  protected bool IsUserScrollingViewer { get; private set; }
82 
83  /// <summary>
84  /// The viewport of the <see cref="ScrollViewer"/> in virtual pixels.
85  /// </summary>
86  public Vector3 ViewPort { get; private set; }
87 
88  /// <summary>
89  /// Gets or sets the value indicating if the element should snap its scrolling to anchors.
90  /// </summary>
91  /// <remarks>Snapping will work only if <see cref="Content"/> implements interface <see cref="IScrollAnchorInfo"/></remarks>
92  public bool SnapToAnchors { get; set; }
93 
94  /// <summary>
95  /// Gets or sets the threshold distance over which a touch move starts scrolling.
96  /// </summary>
97  public float ScrollStartThreshold { get; set; }
98 
99  private Vector3 lastFrameTranslation;
100 
101  private Vector3 accumulatedTranslation;
102 
103  private readonly bool[] startedSnapping = new bool[3];
104 
105  /// <summary>
106  /// The current content casted as <see cref="IScrollInfo"/>.
107  /// </summary>
108  /// <remarks><value>Null</value> if the <see cref="Content"/> does not implement the interface</remarks>
109  protected IScrollInfo ContentAsScrollInfo { get; private set; }
110 
111  /// <summary>
112  /// The current content casted as <see cref="IScrollAnchorInfo"/>
113  /// </summary>
114  /// <remarks><value>Null</value> if the <see cref="Content"/> does not implement the interface</remarks>
115  protected IScrollAnchorInfo ContentAsAnchorInfo { get; private set; }
116 
117  /// <summary>
118  /// The current scroll position (in virtual pixels) of the <see cref="ScrollViewer"/>.
119  /// That is, the position of the left-top-front corner of the <see cref="ScrollViewer"/> in its <see cref="Content"/>.
120  /// </summary>
121  /// <remarks>
122  /// <para>If the <see cref="Content"/> of the scroll viewer implements the <see cref="IScrollInfo"/> interface,
123  /// the <see cref="ScrollPosition"/> will be <value>0</value> in all directions where <see cref="IScrollInfo.CanScroll"/> is true.</para>
124  /// <para>Note that <see cref="ScrollPosition"/> is valid only when <see cref="UIElement.IsArrangeValid"/> is <value>true</value>.
125  /// If <see cref="UIElement.IsArrangeValid"/> is <value>false</value>, <see cref="ScrollPosition"/> contains the position of the scrolling
126  /// before the action that actually invalidated the layout.</para>
127  /// </remarks>
128  public Vector3 ScrollPosition { get { return -ScrollOffsets; } }
129 
130  private static void ValidateDecelarationProperty(ref float value)
131  {
132  if (float.IsNaN(value))
133  throw new ArgumentException("The deceleration must be a valid number. [Deceleration=" + value + "]");
134  }
135 
136  private readonly ScrollBar[] scrollBars =
137  {
138  new ScrollBar { Name = "Left/Right scroll bar"},
139  new ScrollBar { Name = "Top/Bottom scroll bar"},
140  new ScrollBar { Name = "Back/Front scroll bar"}
141  };
142 
143  private struct ScrollRequest
144  {
145  public Vector3 ScrollValue;
146 
147  public bool IsRelative;
148  }
149 
150  /// <summary>
151  /// The list of scrolling requests that need to be performed during the next <see cref="ArrangeOverride"/>
152  /// </summary>
153  private readonly List<ScrollRequest> scrollingRequests = new List<ScrollRequest>();
154 
155  public ScrollViewer()
156  {
157  // put the scroll bars above the presenter and add them to the grid canvas
158  foreach (var bar in scrollBars)
159  {
160  bar.Measure(Vector3.Zero); // set is measure valid to true
161  VisualChildrenCollection.Add(bar);
162  SetVisualParent(bar, this);
163  }
164 
165  ScrollStartThreshold = 10;
166  CanBeHitByUser = TouchScrollingEnabled;
167  ClipToBounds = true;
168  }
169 
170  /// <summary>
171  /// Stops the scrolling at the current position.
172  /// </summary>
173  public void StopCurrentScrolling()
174  {
175  CurrentScrollingSpeed = Vector3.Zero;
176  }
177 
178  /// <summary>
179  /// Indicate if the scroll viewer can scroll in the given direction.
180  /// </summary>
181  /// <param name="direction">The direction to use for the test</param>
182  /// <returns><value>true</value> if the scroll viewer can scroll in the provided direction, or else <value>false</value></returns>
183  public bool CanScroll(Orientation direction)
184  {
185  return orientationToSupportedScrollingModes[(int)direction].Contains(ScrollMode);
186  }
187 
188  private class ScrollBarSorter : Comparer<UIElement>
189  {
190  public override int Compare(UIElement x, UIElement y)
191  {
192  if (x == y)
193  return 0;
194 
195  if (x == null)
196  return 1;
197 
198  if (y == null)
199  return -1;
200 
201  var xVal = x is ScrollBar ? 1 : 0;
202  var yVal = y is ScrollBar ? 1 : 0;
203 
204  return xVal - yVal;
205  }
206  }
207  private static readonly ScrollBarSorter scrollBarSorter = new ScrollBarSorter();
208 
209  private bool userManuallyScrolled;
210 
211  public override UIElement Content
212  {
213  set
214  {
215  if(Content == value)
216  return;
217 
218  // reset scrolling owner of previous object
219  if (ContentAsScrollInfo != null)
220  ContentAsScrollInfo.ScrollOwner = null;
221  if (ContentAsAnchorInfo != null)
222  ContentAsAnchorInfo.ScrollOwner = null;
223 
224  base.Content = value;
225 
226  // reset the current scrolling cache data
227  HideScrollBars();
228  StopCurrentScrolling();
229  ScrollOffsets = Vector3.Zero;
230 
231  // set content scrolling informations
232  ContentAsScrollInfo = value as IScrollInfo;
233  if (ContentAsScrollInfo != null)
234  ContentAsScrollInfo.ScrollOwner = this;
235 
236  // set content anchor in information
237  ContentAsAnchorInfo = value as IScrollAnchorInfo;
238  if (ContentAsAnchorInfo != null)
239  ContentAsAnchorInfo.ScrollOwner = this;
240 
241  VisualChildrenCollection.Sort(scrollBarSorter);
242  }
243  }
244 
245  internal protected void HideScrollBars()
246  {
247  SetScrollBarsColor(ref transparent);
248  }
249 
250  private void SetScrollBarsColor(ref Color color)
251  {
252  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
253  scrollBars[index].BarColor = color;
254  }
255 
256  private static void TouchScrollingEnabledInvalidationCallback(object propertyOwner, PropertyKey<bool> propertyKey, bool propertyOldValue)
257  {
258  var viewer = (ScrollViewer)propertyOwner;
259  viewer.OnTouchScrollingEnabledChanged();
260  }
261 
262  private static void InvalidateScrollMode(object propertyOwner, PropertyKey<ScrollingMode> propertyKey, ScrollingMode propertyOldValue)
263  {
264  var element = (ScrollViewer)propertyOwner;
265  element.OnScrollModeChanged();
266  }
267 
268  /// <summary>
269  /// Method triggered when <see cref="ScrollMode"/> changed.
270  /// Can be overridden in inherited class to change the default behavior.
271  /// </summary>
272  protected virtual void OnScrollModeChanged()
273  {
274  ScrollOffsets = Vector3.Zero;
275 
276  if (ContentAsScrollInfo != null)
277  {
278  ContentAsScrollInfo.ScrollToBeginning(Orientation.Horizontal);
279  ContentAsScrollInfo.ScrollToBeginning(Orientation.Vertical);
280  ContentAsScrollInfo.ScrollToBeginning(Orientation.InDepth);
281  }
282 
283  InvalidateMeasure();
284  }
285 
286  /// <summary>
287  /// Method triggered when <see cref="TouchScrollingEnabled"/> changed.
288  /// Can be overridden in inherited class to change the default behavior.
289  /// </summary>
290  protected virtual void OnTouchScrollingEnabledChanged()
291  {
292  CanBeHitByUser = TouchScrollingEnabled;
293  }
294 
295  /// <summary>
296  /// Gets the scrolling translation that occurred during the last frame
297  /// </summary>
298  protected Vector3 LastFrameTranslation
299  {
300  get { return lastFrameTranslation; }
301  }
302 
303  /// <summary>
304  /// The viewer allowed scrolling mode.
305  /// </summary>
306  public ScrollingMode ScrollMode
307  {
308  get { return DependencyProperties.Get(ScrollModePropertyKey); }
309  set { DependencyProperties.Set(ScrollModePropertyKey, value); }
310  }
311 
312  /// <summary>
313  /// The automatic deceleration of the scroll after the user remove its finger from the screen. The unit is in virtual pixels.
314  /// </summary>
315  public float Deceleration
316  {
317  get { return DependencyProperties.Get(DecelerationPropertyKey); }
318  set { DependencyProperties.Set(DecelerationPropertyKey, value); }
319  }
320 
321  /// <summary>
322  /// Gets or sets the scrolling behavior on touches. True to allow the user to scroll by touching, false to forbid it.
323  /// </summary>
324  public bool TouchScrollingEnabled
325  {
326  get { return DependencyProperties.Get(TouchScrollingEnabledPropertyKey); }
327  set { DependencyProperties.Set(TouchScrollingEnabledPropertyKey, value); }
328  }
329 
330  /// <summary>
331  /// Gets or sets the scrolling behavior on touches. True to allow the user to scroll by touching, false to forbid it.
332  /// </summary>
333  public Color ScrollBarColor
334  {
335  get { return DependencyProperties.Get(ScrollBarColorPropertyKey); }
336  set { DependencyProperties.Set(ScrollBarColorPropertyKey, value); }
337  }
338 
339  /// <summary>
340  /// Gets or sets the scrolling bar thickness in virtual pixels.
341  /// </summary>
342  public float ScrollBarThickness
343  {
344  get { return DependencyProperties.Get(ScrollBarThicknessPropertyKey); }
345  set { DependencyProperties.Set(ScrollBarThicknessPropertyKey, value); }
346  }
347 
348  protected override void Update(GameTime time)
349  {
350  base.Update(time);
351 
352  var elapsedSeconds = (float)time.Elapsed.TotalSeconds;
353  if (elapsedSeconds < MathUtil.ZeroTolerance)
354  return;
355 
356  if (IsUserScrollingViewer || userManuallyScrolled) // scrolling is controlled by the user.
357  {
358  userManuallyScrolled = false;
359  for (int i = 0; i < startedSnapping.Length; i++)
360  startedSnapping[i] = false;
361 
362  if (IsUserScrollingViewer) // compute the scrolling speed based on current translation
363  CurrentScrollingSpeed = LastFrameTranslation / elapsedSeconds;
364  }
365  else // scrolling is free: compute the scrolling translation based on the scrolling speed and anchors
366  {
367  lastFrameTranslation = elapsedSeconds * CurrentScrollingSpeed;
368 
369  if (SnapToAnchors && ContentAsAnchorInfo != null)
370  {
371  for (var i = 0; i < 3; ++i)
372  {
373  if (!ContentAsAnchorInfo.ShouldAnchor((Orientation)i))
374  continue;
375 
376  // get the distance to anchors from the current position.
377  var boundDistances = ContentAsAnchorInfo.GetSurroudingAnchorDistances((Orientation)i, -ScrollOffsets[i]);
378 
379  // determine the closest anchor index
380  var closestAnchorIndex = Math.Abs(boundDistances.X) <= Math.Abs(boundDistances.Y) ? 0 : 1;
381 
382  // check that the anchor can be reached
383  if (closestAnchorIndex == 1)
384  {
385  var offset = ContentAsScrollInfo != null && ContentAsScrollInfo.CanScroll((Orientation)i) ? -ContentAsScrollInfo.Offset[i] : ScrollOffsets[i];
386  var childRenderSize = VisualContent.RenderSize;
387  var childRenderSizeWithMargins = CalculateSizeWithThickness(ref childRenderSize, ref MarginInternal);
388  var childRenderSizeWithPadding = CalculateSizeWithThickness(ref childRenderSizeWithMargins, ref padding);
389  if (offset - boundDistances[1] < ViewPort[i] - childRenderSizeWithPadding[i])
390  closestAnchorIndex = 0;
391  }
392 
393  // set the position manually to the anchor if already very close.
394  if (Math.Abs(CurrentScrollingSpeed[i]) < 5 && Math.Abs(boundDistances[closestAnchorIndex]) < 1) // very slow speed and closer than 1 virtual pixel to anchor
395  {
396  startedSnapping[i] = false;
397  CurrentScrollingSpeed[i] = 0;
398  lastFrameTranslation[i] = boundDistances[closestAnchorIndex];
399  continue;
400  }
401 
402  var snappingSpeed = 5 * boundDistances[closestAnchorIndex];
403  if (startedSnapping[i] || Math.Abs(snappingSpeed) > Math.Abs(CurrentScrollingSpeed[i]))
404  {
405  // set the scrolling speed based on the remaining distance to the closest anchor
406  CurrentScrollingSpeed[i] = snappingSpeed;
407  lastFrameTranslation[i] = elapsedSeconds * CurrentScrollingSpeed[i];
408 
409  startedSnapping[i] = true;
410  }
411  }
412  }
413  }
414 
415  // decrease the scrolling speed used for next frame
416  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
417  CurrentScrollingSpeed[index] = Math.Sign(CurrentScrollingSpeed[index]) * Math.Max(0, Math.Abs(CurrentScrollingSpeed[index]) - elapsedSeconds * Deceleration);
418 
419  // update the scrolling position
420  if(lastFrameTranslation != Vector3.Zero)
421  ScrollOfInternal(ref lastFrameTranslation, false);
422 
423  // Smoothly hide the scroll bars if the no movements
424  for (int dim = 0; dim < 3; dim++)
425  {
426  var shouldFadeOutScrollingBar = Math.Abs(CurrentScrollingSpeed[dim]) < MathUtil.ZeroTolerance && (!TouchScrollingEnabled || !IsUserScrollingViewer);
427  if (shouldFadeOutScrollingBar)
428  for (int i = 0; i < 4; i++)
429  scrollBars[dim].BarColorInternal[i] = (byte)Math.Max(0, scrollBars[dim].BarColorInternal[i] - ScrollBarColor[i] * ScrollBarHidingSpeed * elapsedSeconds);
430  else
431  scrollBars[dim].BarColor = ScrollBarColor;
432  }
433 
434  lastFrameTranslation = Vector3.Zero;
435  }
436 
437  /// <summary>
438  /// Go to the beginning of the scroll viewer's content in the provided direction.
439  /// </summary>
440  /// <param name="direction">The direction in which to scroll</param>
441  /// <param name="stopScrolling">Indicate if the scrolling should be stopped after the scroll action.</param>
442  public void ScrollToBeginning(Orientation direction, bool stopScrolling = true)
443  {
444  ScrollToExtremity(direction, stopScrolling, true);
445  }
446 
447  /// <summary>
448  /// Go to the end of the scroll viewer's content in the provided direction.
449  /// </summary>
450  /// <param name="direction">The direction in which to scroll</param>
451  /// <param name="stopScrolling">Indicate if the scrolling should be stopped after the scroll action.</param>
452  public void ScrollToEnd(Orientation direction, bool stopScrolling = true)
453  {
454  ScrollToExtremity(direction, stopScrolling, false);
455  }
456 
457  private void ScrollToExtremity(Orientation direction, bool stopScrolling, bool isBeginning)
458  {
459  if (stopScrolling)
460  {
461  HideScrollBars();
462  StopCurrentScrolling();
463  }
464 
465  userManuallyScrolled = true;
466 
467  if (!CanScroll(direction))
468  return;
469 
470  if (ContentAsScrollInfo != null && ContentAsScrollInfo.CanScroll(direction)) // scrolling is delegated to Content
471  {
472  if (isBeginning)
473  ContentAsScrollInfo.ScrollToBeginning(direction);
474  else
475  ContentAsScrollInfo.ScrollToEnd(direction);
476  }
477  else // scrolling should be performed by the scroll viewer
478  {
479  var translation = Vector3.Zero;
480  translation[(int)direction] = isBeginning? float.NegativeInfinity: float.PositiveInfinity;
481 
482  ScrollOf(translation, stopScrolling);
483  }
484  }
485 
486  /// <summary>
487  /// Try to scroll to the provided position (in virtual pixels).
488  /// If the provided translation is too important, it is clamped.
489  /// </summary>
490  /// <remarks>Note that the computational cost of <see cref="ScrollTo"/> can be greatly higher than <see cref="ScrollOf"/>
491  /// when scrolling is delegated to a <see cref="Content"/> virtualizing its items. When possible, prefer call to <see cref="ScrollOf"/></remarks>
492  /// <param name="scrollAbsolutePosition">The scroll offsets to apply</param>
493  /// <param name="stopScrolling">Indicate if the scrolling should be stopped after the scroll action.</param>
494  public void ScrollTo(Vector3 scrollAbsolutePosition, bool stopScrolling = true)
495  {
496  if (stopScrolling)
497  {
498  HideScrollBars();
499  StopCurrentScrolling();
500  }
501 
502  userManuallyScrolled = true;
503 
504  if(VisualContent == null)
505  return;
506 
507  // ask the content to internally scroll
508  if (ContentAsScrollInfo != null)
509  {
510  var correctedScrollPosition = Vector3.Zero;
511  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
512  correctedScrollPosition[index] = scrollAbsolutePosition[index];
513 
514  // reset content scroll position to the beginning of the document and then scroll of the absolute position
515  ContentAsScrollInfo.ScrollToBeginning(Orientation.Horizontal);
516  ContentAsScrollInfo.ScrollToBeginning(Orientation.Vertical);
517  ContentAsScrollInfo.ScrollToBeginning(Orientation.InDepth);
518  ContentAsScrollInfo.ScrollOf(correctedScrollPosition);
519  }
520 
521  if (IsArrangeValid) // the children size informations are still valid -> perform scrolling right away
522  {
523  UpdateScrollOffsets(-scrollAbsolutePosition);
524 
525  UpdateVisualContentArrangeMatrix();
526  }
527  else // children may have changed of size -> delay scrolling to next draw call
528  {
529  InvalidateArrange(); // force next arrange to perform scrolls
530  scrollingRequests.Clear(); // optimization remove previous request when provided position is absolute
531  scrollingRequests.Add(new ScrollRequest { ScrollValue = scrollAbsolutePosition });
532  }
533  }
534 
535  /// <summary>
536  /// Try to scroll of the provided scrolling translation value from the current position.
537  /// If the provided translation is too important, it is clamped.
538  /// </summary>
539  /// <param name="scrollTranslation">The scroll translation to perform (in virtual pixels)</param>
540  /// <param name="stopScrolling">Indicate if the scrolling should be stopped after the scroll action.</param>
541  public void ScrollOf(Vector3 scrollTranslation, bool stopScrolling = true)
542  {
543  userManuallyScrolled = true;
544 
545  ScrollOfInternal(ref scrollTranslation, stopScrolling);
546  }
547 
548  public void ScrollOfInternal(ref Vector3 scrollTranslation, bool stopScrolling)
549  {
550  if (stopScrolling)
551  {
552  HideScrollBars();
553  StopCurrentScrolling();
554  }
555 
556  if (VisualContent == null)
557  return;
558 
559  var correctedScrollTranslation = Vector3.Zero;
560  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
561  correctedScrollTranslation[index] = scrollTranslation[index];
562 
563  // ask the content to internally scroll
564  if (ContentAsScrollInfo != null)
565  ContentAsScrollInfo.ScrollOf(correctedScrollTranslation);
566 
567  if (IsArrangeValid) // the children size informations are still valid -> perform scrolling right away
568  {
569  UpdateScrollOffsets(ScrollOffsets - scrollTranslation);
570 
571  UpdateVisualContentArrangeMatrix();
572  }
573  else // children may have changed of size -> delay scrolling to next draw call
574  {
575  InvalidateArrange(); // force next arrange to perform scrolls
576  scrollingRequests.Add(new ScrollRequest { IsRelative = true, ScrollValue = scrollTranslation });
577  }
578  }
579 
580  private void UpdateScrollOffsets(Vector3 desiredScrollPosition)
581  {
582  // the space desired by the child + the padding of the viewer
583  var childRenderSize = VisualContent.RenderSize;
584  var childRenderSizeWithMargins = CalculateSizeWithoutThickness(ref childRenderSize, ref MarginInternal);
585  var childRenderSizeWithPadding = CalculateSizeWithoutThickness(ref childRenderSizeWithMargins, ref padding);
586 
587  // update scroll viewer scroll offsets
588  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
589  {
590  // reset scroll offsets if scrolling is delegated to content
591  if (ContentAsScrollInfo != null && ContentAsScrollInfo.CanScroll((Orientation)index))
592  {
593  ScrollOffsets[index] = 0;
594  continue;
595  }
596 
597  // update the scroll offset
598  ScrollOffsets[index] = desiredScrollPosition[index];
599 
600  // no blank on the other extremity (reached the offset "right" limit)
601  var minimumOffset = ViewPort[index] - childRenderSizeWithPadding[index];
602  if (ScrollOffsets[index] < minimumOffset)
603  {
604  ScrollOffsets[index] = minimumOffset;
605  CurrentScrollingSpeed[index] = 0;
606  }
607 
608  // no positive offsets (reached the offset "left" limit)
609  if (ScrollOffsets[index] > 0)
610  {
611  ScrollOffsets[index] = 0;
612  CurrentScrollingSpeed[index] = 0;
613  }
614  }
615  }
616 
617  public override bool IsEnabled
618  {
619  set
620  {
621  if (!value)
622  HideScrollBars();
623 
624  base.IsEnabled = value;
625  }
626  }
627 
628  protected override Vector3 MeasureOverride(Vector3 availableSizeWithoutMargins)
629  {
630  // measure size desired by the children
631  var childDesiredSizeWithMargins = Vector3.Zero;
632  if (VisualContent != null)
633  {
634  // remove space for padding in availableSizeWithoutMargins
635  var childAvailableSizeWithMargins = CalculateSizeWithoutThickness(ref availableSizeWithoutMargins, ref padding);
636 
637  // if the content is not scrollable perform space virtualization from the scroll viewer side.
638  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
639  {
640  if (ContentAsScrollInfo != null && ContentAsScrollInfo.CanScroll((Orientation)index))
641  continue;
642 
643  childAvailableSizeWithMargins[index] = float.PositiveInfinity;
644  }
645 
646  VisualContent.Measure(childAvailableSizeWithMargins);
647  childDesiredSizeWithMargins = VisualContent.DesiredSizeWithMargins;
648  }
649 
650  // add the padding to the child desired size
651  var desiredSizeWithPadding = CalculateSizeWithThickness(ref childDesiredSizeWithMargins, ref padding);
652 
653  return desiredSizeWithPadding;
654  }
655 
656  protected override Vector3 ArrangeOverride(Vector3 finalSizeWithoutMargins)
657  {
658  // calculate the remaining space for the child after having removed the padding space.
659  ViewPort = finalSizeWithoutMargins;
660 
661  // arrange the content
662  if (VisualContent != null)
663  {
664  // calculate the final size given to the child (scroll view virtual size)
665  var childSizeWithoutPadding = CalculateSizeWithoutThickness(ref finalSizeWithoutMargins, ref padding);
666  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
667  {
668  if (ContentAsScrollInfo == null || !ContentAsScrollInfo.CanScroll((Orientation)index))
669  childSizeWithoutPadding[index] = Math.Max(VisualContent.DesiredSizeWithMargins[index], childSizeWithoutPadding[index]);
670  }
671 
672  // arrange the child
673  VisualContent.Arrange(childSizeWithoutPadding, IsCollapsed);
674 
675  // update the scrolling bars
676  UpdateScrollingBarsSize();
677 
678  // update the scrolling
679  if (scrollingRequests.Count > 0)
680  {
681  // perform the scrolling requests
682  foreach (var request in scrollingRequests)
683  {
684  var scrollPosition = request.IsRelative? ScrollOffsets - request.ScrollValue: -request.ScrollValue;
685  UpdateScrollOffsets(scrollPosition);
686  }
687  }
688  else
689  {
690  UpdateScrollOffsets(ScrollOffsets); // insures that scrolling is not out of bounds
691  }
692 
693  // update the position of the child
694  UpdateVisualContentArrangeMatrix();
695  }
696 
697  scrollingRequests.Clear();
698 
699  return finalSizeWithoutMargins;
700  }
701 
702  private void UpdateScrollingBarsSize()
703  {
704  // reset the bar sizes
705  foreach (var scrollBar in scrollBars)
706  scrollBar.Arrange(Vector3.Zero, false);
707 
708  // set the size of the bar we want to show
709  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
710  {
711  var sizeChildren = (ContentAsScrollInfo != null) ?
712  ContentAsScrollInfo.Extent[index] :
713  VisualContent.RenderSize[index] + VisualContent.MarginInternal[index] + VisualContent.MarginInternal[3 + index];
714 
715  var barLength = Math.Min(1f, ViewPort[index] / sizeChildren) * ViewPort[index];
716 
717  var barSize = Vector3.Zero;
718  for (var dim = 0; dim < 3; dim++)
719  barSize[dim] = dim == index ? barLength : Math.Min(ScrollBarThickness, ViewPort[dim]);
720 
721  scrollBars[index].Arrange(barSize, IsCollapsed);
722  }
723  }
724 
725  private void UpdateVisualContentArrangeMatrix()
726  {
727  // calculate the offsets to move the element of
728  var offsets = ScrollOffsets;
729  for (int i = 0; i < 3; i++)
730  {
731  if (ContentAsScrollInfo != null && ContentAsScrollInfo.CanScroll((Orientation)i))
732  offsets[i] = ContentAsScrollInfo.Offset[i];
733  }
734 
735  // compute the rendering offsets of the child element wrt the parent origin (0,0,0)
736  var childOffsets = offsets + new Vector3(Padding.Left, Padding.Top, Padding.Back) - ViewPort / 2;
737 
738  // set the arrange matrix of the child.
739  VisualContent.DependencyProperties.Set(ContentArrangeMatrixPropertyKey, Matrix.Translation(childOffsets));
740 
741  // force re-calculation of main element and scroll bars world matrices
742  ArrangeChanged = true;
743  }
744 
745  protected override void UpdateWorldMatrix(ref Matrix parentWorldMatrix, bool parentWorldChanged)
746  {
747  var shouldUpdateScrollBars = parentWorldChanged || ArrangeChanged || LocalMatrixChanged;
748 
749  base.UpdateWorldMatrix(ref parentWorldMatrix, parentWorldChanged);
750 
751  // set the world matrices of the scroll bars
752  if (shouldUpdateScrollBars && VisualContent != null)
753  {
754  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
755  {
756  var scrollBar = scrollBars[index];
757  var barPosition = RenderSize / 2 - scrollBar.RenderSize;
758  var childMinusParent = VisualContent.DesiredSizeWithMargins[index] - ViewPort[index];
759 
760  // determine the position ratio of the scroll bar in the viewport
761  var scrollBarPositionRatio = 0f;
762  if (ContentAsScrollInfo != null && ContentAsScrollInfo.CanScroll((Orientation)index))
763  {
764  scrollBarPositionRatio = -ContentAsScrollInfo.ScrollBarPositions[index];
765  }
766  else if (childMinusParent > MathUtil.ZeroTolerance)
767  {
768  scrollBarPositionRatio = ScrollOffsets[index] / childMinusParent;
769  }
770 
771  // adjust the position of the scroll bar
772  barPosition[index] = -(RenderSize[index] / 2 + (scrollBarPositionRatio * (RenderSize[index] - scrollBar.RenderSize[index])));
773 
774  var parentMatrix = WorldMatrix;
775  parentMatrix.TranslationVector += barPosition;
776 
777  ((IUIElementUpdate)scrollBar).UpdateWorldMatrix(ref parentMatrix, true);
778  }
779  }
780  }
781 
782  protected override void OnPreviewTouchDown(TouchEventArgs args)
783  {
784  base.OnPreviewTouchDown(args);
785 
786  StopCurrentScrolling();
787  accumulatedTranslation = Vector3.Zero;
788  }
789 
790  protected override void OnTouchEnter(TouchEventArgs args)
791  {
792  base.OnTouchEnter(args);
793 
794  StopCurrentScrolling();
795  accumulatedTranslation = Vector3.Zero;
796  }
797 
798  protected override void OnTouchLeave(TouchEventArgs args)
799  {
800  base.OnTouchLeave(args);
801 
802  IsUserScrollingViewer = false;
803  VisualContent.PreventChildrenFromBeingHit = false;
804  }
805 
806  protected override void OnPreviewTouchMove(TouchEventArgs args)
807  {
808  base.OnPreviewTouchMove(args);
809 
810  if (ScrollMode == ScrollingMode.None || !TouchScrollingEnabled)
811  return;
812 
813  // accumulate all the touch moves of the frame
814  var translation = args.WorldTranslation;
815  foreach (var index in scrollModeToDirectionIndices[ScrollMode])
816  lastFrameTranslation[index] -= translation[index];
817 
818  accumulatedTranslation += lastFrameTranslation;
819  if (!IsUserScrollingViewer && accumulatedTranslation.Length() > ScrollStartThreshold)
820  {
821  IsUserScrollingViewer = true;
822  lastFrameTranslation = accumulatedTranslation;
823  VisualContent.PreventChildrenFromBeingHit = true;
824  }
825 
826  if (IsUserScrollingViewer)
827  args.Handled = true;
828  }
829 
830  private static void RaiseLeaveTouchEventTohierarchyChildren(UIElement parent, TouchEventArgs args)
831  {
832  if(parent == null)
833  return;
834 
835  var argsCopy = new TouchEventArgs
836  {
837  Action = args.Action,
838  ScreenPosition = args.ScreenPosition,
839  ScreenTranslation = args.ScreenTranslation,
840  Timestamp = args.Timestamp
841  };
842 
843  foreach (var child in parent.VisualChildrenCollection)
844  {
845  if (child.IsTouched)
846  {
847  child.RaiseTouchLeaveEvent(argsCopy);
848  RaiseLeaveTouchEventTohierarchyChildren(child, args);
849  }
850  }
851  }
852 
853  protected override void OnPreviewTouchUp(TouchEventArgs args)
854  {
855  base.OnPreviewTouchUp(args);
856 
857  if (IsUserScrollingViewer)
858  {
859  args.Handled = true;
860  RaiseLeaveTouchEventTohierarchyChildren(this, args);
861  }
862 
863  IsUserScrollingViewer = false;
864  VisualContent.PreventChildrenFromBeingHit = false;
865  }
866 
867  /// <summary>
868  /// Called by an <see cref="IScrollInfo"/> interface that is attached to a <see cref="ScrollViewer"/> when the value of any scrolling property size changes.
869  /// Scrolling properties include offset, extent, or viewport.
870  /// </summary>
871  public void InvalidateScrollInfo()
872  {
873  if(ContentAsScrollInfo == null)
874  return;
875 
876  // reset current scrolling speed if we reached one extrema of the content
877  for (int i = 0; i < 3; i++)
878  {
879  if (ContentAsScrollInfo.ScrollBarPositions[i] < MathUtil.ZeroTolerance || ContentAsScrollInfo.ScrollBarPositions[i] > 1 - MathUtil.ZeroTolerance)
880  CurrentScrollingSpeed[i] = 0f;
881  }
882 
883  UpdateScrollingBarsSize();
884  UpdateVisualContentArrangeMatrix();
885  }
886 
887  /// <summary>
888  /// Called by an <see cref="IScrollAnchorInfo"/> interface that attached to a <see cref="ScrollViewer"/> when the value of any anchor changed.
889  /// </summary>
890  public void InvalidateAnchorInfo()
891  {
892  // currently nothing to do here.
893  }
894  }
895 }
Provides a base class for all the User Interface elements in Paradox applications.
Definition: UIElement.cs:21
Orientation
Defines the different orientations that a control or layout can have.
Definition: Orientation.cs:8
Interface for the update of the UIElements.
override Vector3 MeasureOverride(Vector3 availableSizeWithoutMargins)
When overridden in a derived class, measures the size in layout required for possible child elements ...
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ float size_t y
Definition: DirectXTexP.h:191
override void OnPreviewTouchUp(TouchEventArgs args)
The class handler of the event PreviewTouchUp. This method can be overridden in inherited classes to ...
One bounding volume completely contains another.
const float ZeroTolerance
The value for which all absolute numbers smaller than are considered equal to zero.
Definition: MathUtil.cs:38
Represents a three dimensional mathematical vector.
Definition: Vector3.cs:42
override void OnPreviewTouchDown(TouchEventArgs args)
The class handler of the event PreviewTouchDown. This method can be overridden in inherited classes t...
static void Min(ref Vector3 left, ref Vector3 right, out Vector3 result)
Returns a vector containing the smallest components of the specified vectors.
Definition: Vector3.cs:806
override void Update(GameTime time)
Method called by IUIElementUpdate.Update. This method can be overridden by inherited classes to perfo...
Represents a control with a single piece of content of any type.
void ScrollToEnd(Orientation direction, bool stopScrolling=true)
Go to the end of the scroll viewer's content in the provided direction.
override void OnPreviewTouchMove(TouchEventArgs args)
The class handler of the event PreviewTouchMove. This method can be overridden in inherited classes t...
Interface providing anchor information to its ScrollViewer.
ScrollingMode
The different ways of scrolling in a ScrollViewer.
static readonly Vector3 Zero
A SiliconStudio.Core.Mathematics.Vector3 with all of its components set to zero.
Definition: Vector3.cs:52
virtual void OnTouchScrollingEnabledChanged()
Method triggered when TouchScrollingEnabled changed. Can be overridden in inherited class to change t...
virtual void OnScrollModeChanged()
Method triggered when ScrollMode changed. Can be overridden in inherited class to change the default ...
Current timing used for variable-step (real time) or fixed-step (game time) games.
Definition: GameTime.cs:31
SiliconStudio.Core.Mathematics.Color Color
Definition: ColorPicker.cs:14
void ScrollOfInternal(ref Vector3 scrollTranslation, bool stopScrolling)
void ScrollOf(Vector3 scrollTranslation, bool stopScrolling=true)
Try to scroll of the provided scrolling translation value from the current position. If the provided translation is too important, it is clamped.
UIElementCollection VisualChildrenCollection
The visual children of this element.
Definition: UIElement.cs:274
Represents the main scrollable region inside a ScrollViewer control.
Definition: IScrollInfo.cs:11
Represents a 32-bit color (4 bytes) in the form of RGBA (in byte order: R, G, B, A).
Definition: Color.cs:16
void ScrollToBeginning(Orientation direction, bool stopScrolling=true)
Go to the beginning of the scroll viewer's content in the provided direction.
override void UpdateWorldMatrix(ref Matrix parentWorldMatrix, bool parentWorldChanged)
Method called by IUIElementUpdate.UpdateWorldMatrix. Parents are in charge of recursively calling thi...
Provides data for touch input events.
override void OnTouchLeave(TouchEventArgs args)
The class handler of the event TouchLeave. This method can be overridden in inherited classes to perf...
Only valid for a property / field that has a class or struct type. When restored, instead of recreati...
SiliconStudio.Core.Mathematics.Vector3 Vector3
bool CanScroll(Orientation direction)
Indicate if the scroll viewer can scroll in the given direction.
override void OnTouchEnter(TouchEventArgs args)
The class handler of the event TouchEnter. This method can be overridden in inherited classes to perf...
TimeSpan Elapsed
Gets the elapsed game time since the last update
Definition: GameTime.cs:80
void ScrollTo(Vector3 scrollAbsolutePosition, bool stopScrolling=true)
Try to scroll to the provided position (in virtual pixels). If the provided translation is too import...
Android.Widget.Orientation Orientation
Definition: Section.cs:9
Represents a scroll viewer. A scroll viewer element has an infinite virtual size defined by its Scrol...
Definition: ScrollViewer.cs:19
A class that represents a tag propety.
Definition: PropertyKey.cs:17
void StopCurrentScrolling()
Stops the scrolling at the current position.
void InvalidateAnchorInfo()
Called by an IScrollAnchorInfo interface that attached to a ScrollViewer when the value of any anchor...
Vector3 ScrollOffsets
The current offsets (in virtual pixels) generated by the scrolling on the ContentControl.Content element.
Definition: ScrollViewer.cs:71
Vector3 CurrentScrollingSpeed
The current speed of the scrolling in virtual pixels.
Definition: ScrollViewer.cs:76
void InvalidateScrollInfo()
Called by an IScrollInfo interface that is attached to a ScrollViewer when the value of any scrolling...
override Vector3 ArrangeOverride(Vector3 finalSizeWithoutMargins)
When overridden in a derived class, positions possible child elements and determines a size for a UIE...
Represents a 4x4 mathematical matrix.
Definition: Matrix.cs:47