Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
StackPanel.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 using SiliconStudio.Core;
7 using SiliconStudio.Core.Mathematics;
8 
9 namespace SiliconStudio.Paradox.UI.Panels
10 {
11  /// <summary>
12  /// Arranges child elements into a single line that can be oriented horizontally or vertically.
13  /// </summary>
14  [DebuggerDisplay("StackPanel - Name={Name}")]
15  public class StackPanel : Panel, IScrollInfo
16  {
17  /// <summary>
18  /// The key to the Orientation dependency property.
19  /// </summary>
20  public readonly static PropertyKey<Orientation> OrientationPropertyKey = new PropertyKey<Orientation>("OrientationKey", typeof(StackPanel), DefaultValueMetadata.Static(Orientation.Vertical), ObjectInvalidationMetadata.New<Orientation>(InvalidationCallback));
21 
22  /// <summary>
23  /// Indicate the first index of Vector3 to use to maximize depending on the stack panel orientation.
24  /// </summary>
25  protected static readonly int[] OrientationToMaximizeIndex1 = { 1, 0, 0 };
26  /// <summary>
27  /// Indicate the second index of Vector3 to use to accumulate depending on the stack panel orientation.
28  /// </summary>
29  protected static readonly int[] OrientationToMaximizeIndex2 = { 2, 2, 1 };
30  /// <summary>
31  /// Indicate the axis along which the measure zone is infinite depending on the scroll owner scrolling mode.
32  /// </summary>
33  protected static readonly List<int[]> ScrollingModeToInfiniteAxis = new List<int[]>
34  {
35  new int[0],
36  new []{ 0 },
37  new []{ 1 },
38  new []{ 2 },
39  new []{ 0, 1 },
40  new []{ 1, 2 },
41  new []{ 2, 0 },
42  };
43 
44  private Vector3 offset;
45 
46  /// <summary>
47  /// The current scroll position of the top/left corner.
48  /// </summary>
49  private float scrollPosition;
50 
51  /// <summary>
52  /// The current scroll position of the left/top corner of the stack panel.
53  /// </summary>
54  /// <remarks>The stack panel scroll position is expressed element index units.
55  /// For example: 0 represents the first element, 1 represents the second element, 1.33 represents a third of the second element, etc...</remarks>
56  public float ScrollPosition { get { return scrollPosition; } }
57 
58  private bool itemVirtualizationEnabled;
59 
60  /// <summary>
61  /// The list of the visible children having the same order as in <see cref="Panel.Children"/>.
62  /// </summary>
63  /// <remarks>This list is valid on when <see cref="ItemVirtualizationEnabled"/> is <value>true</value></remarks>
64  private readonly List<UIElement> visibleChildren = new List<UIElement>();
65 
66  private readonly List<UIElement> cachedVisibleChildren = new List<UIElement>();
67 
68  private Vector3 extent;
69 
70  private readonly List<float> elementBounds = new List<float>();
71 
72  /// <summary>
73  /// The 0-based index of the last element that can be scrolled to. This value is valid only if <see cref="ItemVirtualizationEnabled"/> is false.
74  /// </summary>
75  private int indexElementMaxScrolling;
76 
77  /// <summary>
78  /// Gets or sets the value indicating if the <see cref="StackPanel"/> children must be virtualized or not.
79  /// When children virtualization is activated, hided children's measurement, arrangement and draw are avoided.
80  /// </summary>
81  public bool ItemVirtualizationEnabled
82  {
83  get { return itemVirtualizationEnabled; }
84  set
85  {
86  if(itemVirtualizationEnabled == value)
87  return;
88 
89  itemVirtualizationEnabled = value;
90 
91  // recreate the visual children collection with all the stack children if virtualization is disabled
92  if (!itemVirtualizationEnabled)
93  {
94  // remove the partial list of visible children
95  while (VisualChildrenCollection.Count > 0)
96  SetVisualParent(VisualChildrenCollection[0], null);
97 
98  // add all of them back
99  foreach (var child in Children)
100  SetVisualParent(child, this);
101 
102  // resort the children by z-order
103  VisualChildrenCollection.Sort(PanelChildrenSorter);
104  }
105  else
106  {
107  visibleChildren.Clear();
108  }
109 
110  InvalidateMeasure();
111  }
112  }
113 
114  private enum ScrollRequestType
115  {
116  AbsolutePosition,
117  RelativeElement,
118  RelativePosition,
119  }
120 
121  private struct ScrollRequest
122  {
123 
124  public float ScrollValue;
125  public ScrollRequestType Type;
126  }
127 
128  private readonly List<ScrollRequest> scrollingRequets = new List<ScrollRequest>();
129 
130  /// <summary>
131  /// Estimate the length of the extent from the visible elements.
132  /// </summary>
133  /// <remarks>This should be used only when item virtualization is enabled</remarks>
134  /// <returns>The estimated size of the extent</returns>
135  private float EstimateExtentLength()
136  {
137  var scrollAxis = (int)Orientation;
138 
139  // estimate the size of the extent using the last elements of the list
140  // we use always those elements in order to have a constant extent size estimation and because those sizes are also pre-calculated by ScrollBarPosition
141 
142  var indexElement = Children.Count;
143  var accumulatedSize = 0f;
144  while (indexElement > 0 && accumulatedSize < Viewport[scrollAxis])
145  {
146  --indexElement;
147  accumulatedSize += GetSafeChildSize(indexElement, scrollAxis);
148  }
149 
150  // calculate the size taken by all elements if proportional
151  return accumulatedSize / (Children.Count - indexElement) * Children.Count;
152  }
153 
154  private static void InvalidationCallback(object propertyOwner, PropertyKey<Orientation> propertyKey, Orientation propertyOldValue)
155  {
156  var element = (UIElement)propertyOwner;
157  element.InvalidateMeasure();
158  }
159 
160  /// <summary>
161  /// Gets or sets a value that indicates the orientation by which child elements are stacked.
162  /// </summary>
163  public Orientation Orientation
164  {
165  get { return DependencyProperties.Get(OrientationPropertyKey); }
166  set { DependencyProperties.Set(OrientationPropertyKey, value); }
167  }
168 
169  protected override void OnLogicalChildRemoved(UIElement oldElement, int index)
170  {
171  base.OnLogicalChildRemoved(oldElement, index);
172 
173  if (index < (int)Math.Floor(scrollPosition))
174  --scrollPosition;
175  }
176 
177  protected override void OnLogicalChildAdded(UIElement newElement, int index)
178  {
179  base.OnLogicalChildAdded(newElement, index);
180 
181  if (index < (int)Math.Floor(scrollPosition))
182  ++scrollPosition;
183  }
184 
185  protected override Vector3 MeasureOverride(Vector3 availableSizeWithoutMargins)
186  {
187  Viewport = availableSizeWithoutMargins;
188 
189  // update the visible children if item virtualization is enabled.
190  if(ItemVirtualizationEnabled)
191  AdjustOffsetsAndVisualChildren(scrollPosition);
192 
193  var accumulatorIndex = (int)Orientation;
194  var maximizeIndex1 = OrientationToMaximizeIndex1[(int)Orientation];
195  var maximizeIndex2 = OrientationToMaximizeIndex2[(int)Orientation];
196 
197  // compute the size available to the children depending on the stack orientation
198  var childAvailableSizeWithMargins = availableSizeWithoutMargins;
199  childAvailableSizeWithMargins[accumulatorIndex] = float.PositiveInfinity;
200 
201  // add infinite bounds depending on scroll owner scrolling mode
202  if (ScrollOwner != null)
203  {
204  foreach (var i in ScrollingModeToInfiniteAxis[(int)ScrollOwner.ScrollMode])
205  childAvailableSizeWithMargins[i] = float.PositiveInfinity;
206  }
207 
208  // measure all the children
209  var children = ItemVirtualizationEnabled ? visibleChildren : Children.UnderlyingList;
210  foreach (var child in children)
211  child.Measure(childAvailableSizeWithMargins);
212 
213  // calculate the stack panel desired size
214  var desiredSize = Vector3.Zero;
215  foreach (var child in children)
216  {
217  desiredSize[accumulatorIndex] += child.DesiredSizeWithMargins[accumulatorIndex];
218  desiredSize[maximizeIndex1] = Math.Max(desiredSize[maximizeIndex1], child.DesiredSizeWithMargins[maximizeIndex1]);
219  desiredSize[maximizeIndex2] = Math.Max(desiredSize[maximizeIndex2], child.DesiredSizeWithMargins[maximizeIndex2]);
220  }
221 
222  return desiredSize;
223  }
224 
225  protected override Vector3 ArrangeOverride(Vector3 finalSizeWithoutMargins)
226  {
227  visibleChildren.Clear(); // children's children may have changed we need to force the rearrangement.
228 
229  Viewport = finalSizeWithoutMargins;
230 
231  // determine the stack panel axis
232  var stackAxis = (int)Orientation;
233 
234  // re-arrange all children and update item position cache data when virtualization is off
235  if (!ItemVirtualizationEnabled)
236  {
237  ArrangeChildren();
238 
239  // determine the index of the last element that we can scroll to
240  indexElementMaxScrolling = elementBounds.Count - 2;
241  while (indexElementMaxScrolling > 0 && elementBounds[indexElementMaxScrolling] > elementBounds[elementBounds.Count-1] - Viewport[stackAxis])
242  --indexElementMaxScrolling;
243  }
244 
245  // update the extent (extent need to be valid before updating scrolling)
246  extent = finalSizeWithoutMargins;
247  if (ItemVirtualizationEnabled)
248  extent[stackAxis] = EstimateExtentLength();
249  else
250  extent[stackAxis] = elementBounds[elementBounds.Count - 1];
251 
252  // Update the scrolling
253  if (scrollingRequets.Count > 0) // perform scroll requests
254  {
255  foreach (var request in scrollingRequets)
256  {
257  switch (request.Type)
258  {
259  case ScrollRequestType.AbsolutePosition:
260  ScrolllToElement(request.ScrollValue);
261  break;
262  case ScrollRequestType.RelativeElement:
263  ScrolllToNeigbourElement(Orientation, request.ScrollValue);
264  break;
265  case ScrollRequestType.RelativePosition:
266  ScrollOf(request.ScrollValue);
267  break;
268  default:
269  throw new ArgumentOutOfRangeException();
270  }
271  }
272  }
273  else // update children and scrolling info (mainly offsets)
274  {
275  AdjustOffsetsAndVisualChildren(scrollPosition);
276  }
277  scrollingRequets.Clear();
278 
279  // invalidate anchor info
280  if (ScrollOwner != null)
281  ScrollOwner.InvalidateAnchorInfo();
282 
283  return finalSizeWithoutMargins;
284  }
285 
286  private void ArrangeChildren()
287  {
288  // reset the anchor bounds
289  elementBounds.Clear();
290 
291  // cache the accumulator and maximize indices
292  var accumulatorIndex = (int)Orientation;
293  var maximizeIndex1 = OrientationToMaximizeIndex1[(int)Orientation];
294  var maximizeIndex2 = OrientationToMaximizeIndex2[(int)Orientation];
295 
296  // add the first element bound
297  elementBounds.Add(0);
298 
299  // arrange all the children
300  var children = ItemVirtualizationEnabled ? visibleChildren : Children.UnderlyingList;
301  foreach (var child in children)
302  {
303  var startBound = elementBounds[elementBounds.Count - 1];
304 
305  // compute the child origin
306  var childOrigin = -Viewport / 2; // correspond to (left, top, back) parent corner
307  childOrigin[accumulatorIndex] += startBound;
308 
309  // set the arrange matrix of the child
310  child.DependencyProperties.Set(PanelArrangeMatrixPropertyKey, Matrix.Translation(childOrigin));
311 
312  // compute the size given to the child
313  var childSizeWithMargins = child.DesiredSizeWithMargins;
314  childSizeWithMargins[maximizeIndex1] = Viewport[maximizeIndex1];
315  childSizeWithMargins[maximizeIndex2] = Viewport[maximizeIndex2];
316 
317  // arrange the child
318  child.Arrange(childSizeWithMargins, IsCollapsed);
319 
320  // add the next element bound
321  if (child.IsCollapsed)
322  elementBounds.Add(startBound);
323  else
324  elementBounds.Add(startBound + child.RenderSize[accumulatorIndex] + child.MarginInternal[accumulatorIndex] + child.MarginInternal[3 + accumulatorIndex]);
325  }
326  }
327 
328  public bool CanScroll(Orientation direction)
329  {
330  return direction == Orientation;
331  }
332 
333  public Vector3 Extent { get { return extent; } }
334 
335  public Vector3 Offset { get { return offset; } }
336 
337  public Vector3 Viewport { get; private set; }
338 
339  public override Vector2 GetSurroudingAnchorDistances(Orientation direction, float position)
340  {
341  if (direction != Orientation)
342  return base.GetSurroudingAnchorDistances(direction, position);
343 
344  Vector2 distances;
345 
346  GetDistanceToSurroundingAnchors((int)direction, out distances);
347 
348  return distances;
349  }
350 
351  private void GetDistanceToSurroundingAnchors(int axisIndex, out Vector2 distances)
352  {
353  var currentElementIndex = (int)Math.Floor(scrollPosition);
354  var currentElementRatio = scrollPosition - currentElementIndex;
355  var currentElementSize = GetSafeChildSize(currentElementIndex, axisIndex);
356  var elementSizeRatio = currentElementRatio * currentElementSize;
357 
358  distances = new Vector2(-elementSizeRatio, currentElementSize - elementSizeRatio);
359  }
360 
361  /// <summary>
362  /// Jump to the element having the provided index.
363  /// </summary>
364  /// <param name="elementIndex">The index (0-based) of the element in the stack panel to jump to</param>
365  public void ScrolllToElement(float elementIndex)
366  {
367  if (IsArrangeValid)
368  {
369  AdjustOffsetsAndVisualChildren(elementIndex);
370  }
371  else
372  {
373  InvalidateArrange(); // force next arrange so that requests can be processed.
374  scrollingRequets.Clear(); // optimization delete previous requests when the request is absolute
375  scrollingRequets.Add(new ScrollRequest { ScrollValue = elementIndex });
376  }
377  }
378 
379  private void ScrolllToElement(Orientation orientation, float elementIndex)
380  {
381  if(Orientation != orientation)
382  return;
383 
384  ScrolllToElement(elementIndex);
385  }
386 
387  public void ScrollOf(Vector3 offsetsToApply)
388  {
389  ScrollOf(offsetsToApply[(int)Orientation]);
390  }
391 
392  /// <summary>
393  /// Scroll of the provided offset from the current position in the direction given by the stack panel <see cref="Orientation"/> .
394  /// </summary>
395  /// <param name="offsetToApply">The value to scroll off</param>
396  public void ScrollOf(float offsetToApply)
397  {
398  var axis = (int)Orientation;
399 
400  var absOffsetToApply = Math.Abs(offsetToApply);
401 
402  if (absOffsetToApply < MathUtil.ZeroTolerance)
403  return;
404 
405  if (IsArrangeValid)
406  {
407  // perform the scrolling request is arrange (Viewport mainly) is still valid.
408  var newElementIndex = (int)Math.Floor(scrollPosition);
409  var currentPositionChildSize = GetSafeChildSize(newElementIndex, axis);
410  var currentOffsetInChild = (scrollPosition - newElementIndex) * currentPositionChildSize;
411 
412  var scrollForward = offsetToApply > 0;
413  var previousElementAccumulatedSize = scrollForward ? -currentOffsetInChild : currentOffsetInChild - currentPositionChildSize;
414  var newElementSize = currentPositionChildSize;
415 
416  while (previousElementAccumulatedSize + newElementSize < absOffsetToApply && (scrollForward ? newElementIndex < Children.Count - 1 : newElementIndex > 0))
417  {
418  newElementIndex += Math.Sign(offsetToApply);
419  previousElementAccumulatedSize += newElementSize;
420  newElementSize = GetSafeChildSize(newElementIndex, axis);
421  }
422 
423  var offsetToApplyRemainder = absOffsetToApply - previousElementAccumulatedSize;
424  var partialChildSize = scrollForward ? offsetToApplyRemainder : newElementSize - offsetToApplyRemainder;
425  var newScrollPosition = newElementIndex + partialChildSize / newElementSize;
426 
427  AdjustOffsetsAndVisualChildren(newScrollPosition);
428  }
429  else
430  {
431  // delay the scrolling request to next draw when arrange info (mainly Viewport) will be valid again.
432  InvalidateArrange(); // force next arrange so that requests can be processed.
433  scrollingRequets.Add(new ScrollRequest { ScrollValue = offsetToApply, Type = ScrollRequestType.RelativePosition});
434  }
435  }
436 
437  public Vector3 ScrollBarPositions
438  {
439  get
440  {
441  var positionRatio = Vector3.Zero;
442  var scrollAxis = (int)Orientation;
443 
444  if (Children.Count == 0)
445  return positionRatio;
446 
447  var extentMinusViewport = extent[scrollAxis] - Viewport[scrollAxis];
448  if (extentMinusViewport <= MathUtil.ZeroTolerance)
449  return positionRatio;
450 
451  if (ItemVirtualizationEnabled)
452  {
453  // determine the last scroll position reachable
454  var indexElement = Children.Count;
455  var accumulatedSize = 0f;
456  while (indexElement > 0 && accumulatedSize < Viewport[scrollAxis])
457  {
458  --indexElement;
459  accumulatedSize += GetSafeChildSize(indexElement, scrollAxis);
460  }
461  var maxScrollPosition = Math.Max(0, indexElement + (accumulatedSize-Viewport[scrollAxis]) / GetSafeChildSize(indexElement, scrollAxis));
462  positionRatio[scrollAxis] = scrollPosition / maxScrollPosition;
463  }
464  else
465  {
466  var elementIndex = (int)Math.Floor(scrollPosition);
467  var elementRemainder = scrollPosition - elementIndex;
468 
469  if (elementBounds.Count > elementIndex + 1)
470  {
471  var previousPosition = elementBounds[elementIndex];
472  var nextPosition = elementBounds[elementIndex + 1];
473  positionRatio[scrollAxis] = Math.Min(1, (previousPosition + elementRemainder * (nextPosition - previousPosition)) / extentMinusViewport);
474  }
475  }
476 
477  return positionRatio;
478  }
479  }
480 
481  /// <summary>
482  /// Scroll to the next element of the <see cref="StackPanel"/>.
483  /// </summary>
484  /// <remarks>Equivalent to <see cref="ScrollToNextLine(UI.Orientation)"/> called with the value <see cref="Orientation"/></remarks>
485  public void ScrollToNextLine()
486  {
487  ScrolllToNeigbourElement(Orientation, 1);
488  }
489 
490  /// <summary>
491  /// Scroll to the previous element of the <see cref="StackPanel"/>.
492  /// </summary>
493  /// <remarks>Equivalent to <see cref="ScrollToPreviousLine(UI.Orientation)"/> called with the value <see cref="Orientation"/></remarks>
494  public void ScrollToPreviousLine()
495  {
496  ScrolllToNeigbourElement(Orientation, -1);
497  }
498 
499  public void ScrollToNextLine(Orientation direction)
500  {
501  ScrolllToNeigbourElement(direction, 1);
502  }
503 
504  public void ScrollToPreviousLine(Orientation direction)
505  {
506  ScrolllToNeigbourElement(direction, -1);
507  }
508 
509  private void ScrolllToNeigbourElement(Orientation direction, float side)
510  {
511  if(direction != Orientation)
512  return;
513 
514  if (IsArrangeValid)
515  {
516  AdjustOffsetsAndVisualChildren((float)(side > 0? Math.Floor(scrollPosition + 1) : Math.Ceiling(scrollPosition - 1)));
517  }
518  else
519  {
520  InvalidateArrange(); // force next arrange so that requests can be processed.
521  scrollingRequets.Add(new ScrollRequest { ScrollValue = side, Type = ScrollRequestType.RelativeElement });
522  }
523  }
524 
525  /// <summary>
526  /// Scroll to the next page of elements of the <see cref="StackPanel"/>.
527  /// </summary>
528  /// <remarks>Equivalent to <see cref="ScrollToNextPage(UI.Orientation)"/> called with the value <see cref="Orientation"/></remarks>
529  public void ScrollToNextPage()
530  {
531  ScrollPages(Orientation, 1);
532  }
533 
534  /// <summary>
535  /// Scroll to the previous page of elements of the <see cref="StackPanel"/>.
536  /// </summary>
537  /// <remarks>Equivalent to <see cref="ScrollToPreviousPage(UI.Orientation)"/> called with the value <see cref="Orientation"/></remarks>
538  public void ScrollToPreviousPage()
539  {
540  ScrollPages(Orientation, -1);
541  }
542 
543  public void ScrollToNextPage(Orientation direction)
544  {
545  ScrollPages(direction, 1);
546  }
547 
548  public void ScrollToPreviousPage(Orientation direction)
549  {
550  ScrollPages(direction, -1);
551  }
552 
553  /// <summary>
554  /// Scroll to the beginning of the <see cref="StackPanel"/>.
555  /// </summary>
556  /// <remarks>Equivalent to <see cref="ScrollToBeginning(UI.Orientation)"/> called with the value <see cref="Orientation"/></remarks>
557  public void ScrollToBeginning()
558  {
559  ScrolllToElement(Orientation, 0);
560  }
561 
562  /// <summary>
563  /// Scroll to the end of the <see cref="StackPanel"/>.
564  /// </summary>
565  /// <remarks>Equivalent to <see cref="ScrollToEnd(UI.Orientation)"/> called with the value <see cref="Orientation"/></remarks>
566  public void ScrollToEnd()
567  {
568  ScrolllToElement(Orientation, int.MaxValue);
569  }
570 
571  public void ScrollToBeginning(Orientation direction)
572  {
573  ScrolllToElement(direction, 0);
574  }
575 
576  public void ScrollToEnd(Orientation direction)
577  {
578  ScrolllToElement(direction, int.MaxValue);
579  }
580 
581  private void ScrollPages(Orientation direction, float numberOfPages)
582  {
583  if (direction != Orientation)
584  return;
585 
586  ScrollOf(numberOfPages * Viewport[(int)Orientation]);
587  }
588 
589  /// <summary>
590  /// This function adjust the current first element index, the current offsets and the current visual children collection to have a valid stack display.
591  /// </summary>
592  private void AdjustOffsetsAndVisualChildren(float desiredNewScrollPosition)
593  {
594  offset = Vector3.Zero;
595  var axis = (int)Orientation;
596 
597  if (ItemVirtualizationEnabled)
598  {
599  UpdateScrollPosition(desiredNewScrollPosition);
600  UpdateAndArrangeVisibleChildren();
601  }
602  else // no item virtualization
603  {
604  if (elementBounds.Count < 2) // no children
605  {
606  scrollPosition = 0;
607  offset = Vector3.Zero;
608  }
609  else
610  {
611  var viewportSize = Viewport[axis];
612  var inferiorBound = elementBounds[indexElementMaxScrolling];
613  var superiorBound = elementBounds[indexElementMaxScrolling + 1];
614  var boundDifference = superiorBound - inferiorBound;
615 
616  // calculate the maximum scroll position
617  float maxScrollPosition = indexElementMaxScrolling;
618  if (boundDifference > MathUtil.ZeroTolerance)
619  maxScrollPosition += Math.Min(1 - MathUtil.ZeroTolerance, (extent[axis] - viewportSize - inferiorBound) / boundDifference);
620 
621  // set a valid scroll position
622  scrollPosition = Math.Max(0, Math.Min(maxScrollPosition, desiredNewScrollPosition));
623 
624  // add the first element start bound as initial scroll offset
625  var firstElementIndex = (int)Math.Floor(scrollPosition);
626  offset[axis] = -elementBounds[firstElementIndex];
627 
628  // update the visible element list for hit tests
629  visibleChildren.Clear();
630  for (var i = firstElementIndex; i < Children.Count; i++)
631  {
632  visibleChildren.Add(Children[i]);
633  if (elementBounds[i+1] - elementBounds[firstElementIndex+1] > viewportSize)
634  break;
635  }
636  }
637  }
638 
639  // adjust the offset of the children
640  var scrollPositionIndex = (int)Math.Floor(scrollPosition);
641  var scrollPositionRemainder = scrollPosition - scrollPositionIndex;
642  offset[axis] -= scrollPositionRemainder * GetSafeChildSize(scrollPositionIndex, axis);
643 
644  // force the scroll owner to update the scroll info
645  if(ScrollOwner != null)
646  ScrollOwner.InvalidateScrollInfo();
647  }
648 
649  private void UpdateAndArrangeVisibleChildren()
650  {
651  var axis = (int)Orientation;
652 
653  // cache the initial list of children
654  cachedVisibleChildren.Clear();
655  foreach (var child in visibleChildren)
656  cachedVisibleChildren.Add(child);
657 
658  // reset the list
659  visibleChildren.Clear();
660 
661  // remove all the current visual children
662  while (VisualChildrenCollection.Count > 0)
663  SetVisualParent(VisualChildrenCollection[0], null);
664 
665  // determine first element index and size
666  var elementIndex = (int)Math.Floor(scrollPosition);
667  var firstChildSize = GetSafeChildSize(elementIndex, axis);
668 
669  // create the next visual children collection to display
670  var currentSize = -(scrollPosition - elementIndex) * firstChildSize;
671  while (elementIndex < Children.Count && currentSize <= Viewport[axis])
672  {
673  currentSize += GetSafeChildSize(elementIndex, axis);
674 
675  visibleChildren.Add(Children[elementIndex]);
676  SetVisualParent(Children[elementIndex], this);
677  ++elementIndex;
678  }
679 
680  // reorder visual children by z-Order
681  VisualChildrenCollection.Sort(PanelChildrenSorter);
682 
683  // re-arrange the children if they changed
684  if (visibleChildren.Count > 0)
685  {
686  var shouldRearrangeChildren = cachedVisibleChildren.Count == 0 || cachedVisibleChildren.Count != visibleChildren.Count;
687 
688  // determine if the two list are equals
689  if (!shouldRearrangeChildren)
690  {
691  for (int i = 0; i < visibleChildren.Count; i++)
692  {
693  if (cachedVisibleChildren[i] != visibleChildren[i])
694  {
695  shouldRearrangeChildren = true;
696  break;
697  }
698  }
699  }
700  if(shouldRearrangeChildren)
701  ArrangeChildren();
702  }
703  }
704 
705  private void UpdateScrollPosition(float newScrollPosition)
706  {
707  var axis = (int)Orientation;
708  var viewportSize = Viewport[axis];
709 
710  // determine a valid scroll position
711  var validNextScrollPosition = Math.Max(0, Math.Min(Children.Count - MathUtil.ZeroTolerance, newScrollPosition));
712  var firstElementIndex = (int)Math.Floor(validNextScrollPosition);
713  var startOffset = (validNextScrollPosition - firstElementIndex) * GetSafeChildSize(firstElementIndex, axis);
714  var currentSize = -startOffset;
715 
716  // check if there are enough element after to fill the viewport
717  var currentElementIndex = firstElementIndex;
718  while (currentElementIndex < Children.Count && currentSize < viewportSize)
719  {
720  currentSize += GetSafeChildSize(currentElementIndex, axis);
721  ++currentElementIndex;
722  }
723 
724  // move the valid scroll position backward if not event space to fill the viewport
725  if (currentSize < viewportSize)
726  {
727  currentSize += startOffset - GetSafeChildSize(firstElementIndex, axis); // remove partial size of first element
728  while (firstElementIndex >= 0)
729  {
730  var elementSize = GetSafeChildSize(firstElementIndex, axis);
731  currentSize += elementSize;
732 
733  if (currentSize >= viewportSize)
734  break;
735 
736  --firstElementIndex;
737  }
738 
739  if (firstElementIndex < 0) // all the elements of the stack panel together are smaller than the viewport.
740  {
741  validNextScrollPosition = 0;
742  }
743  else
744  {
745  var firstElementSize = GetSafeChildSize(firstElementIndex, axis);
746  validNextScrollPosition = firstElementIndex + (currentSize - viewportSize) / firstElementSize;
747  }
748  }
749 
750  // update the current scroll position
751  scrollPosition = validNextScrollPosition;
752  }
753 
754  private float GetSafeChildSize(int childIndex, int dimension)
755  {
756  if (childIndex >= Children.Count)
757  return 0;
758 
759  var child = Children[childIndex];
760 
761  if (child.IsCollapsed)
762  return 0f;
763 
764  if (!child.IsMeasureValid)
765  {
766  var childProvidedSize = Viewport;
767 
768  if (ScrollOwner != null)
769  {
770  foreach (var i in ScrollingModeToInfiniteAxis[(int)ScrollOwner.ScrollMode])
771  childProvidedSize[i] = float.PositiveInfinity;
772  }
773 
774  child.Measure(childProvidedSize);
775  }
776 
777  if (!child.IsArrangeValid)
778  {
779  var childProvidedSize = Viewport;
780  childProvidedSize[(int)Orientation] = child.DesiredSizeWithMargins[(int)Orientation];
781 
782  child.Arrange(childProvidedSize, Parent != null && Parent.IsCollapsed);
783  }
784 
785  return child.RenderSize[dimension] + child.Margin[dimension] + child.Margin[dimension + 3];
786  }
787 
788  protected internal override List<UIElement> HitableChildren
789  {
790  get { return visibleChildren; }
791  }
792  }
793 }
void ScrollToNextLine()
Scroll to the next element of the StackPanel.
Definition: StackPanel.cs:485
void ScrollToEnd(Orientation direction)
Go to the end of the element in the given the direction.
Definition: StackPanel.cs:576
Provides a base class for all the User Interface elements in Paradox applications.
Definition: UIElement.cs:21
SiliconStudio.Paradox.Games.Mathematics.Vector2 Vector2
void ScrollOf(float offsetToApply)
Scroll of the provided offset from the current position in the direction given by the stack panel Ori...
Definition: StackPanel.cs:396
void ScrollToNextLine(Orientation direction)
Go to the next line in the given the direction.
Definition: StackPanel.cs:499
void ScrollOf(Vector3 offsetsToApply)
Increase the amount of offset from the current scrolling position.
Definition: StackPanel.cs:387
Represents a two dimensional mathematical vector.
Definition: Vector2.cs:42
void ScrollToNextPage()
Scroll to the next page of elements of the StackPanel.
Definition: StackPanel.cs:529
void ScrollToBeginning()
Scroll to the beginning of the StackPanel.
Definition: StackPanel.cs:557
override Vector2 GetSurroudingAnchorDistances(Orientation direction, float position)
Get the distances to the previous and next anchors in the provided direction and from given position...
Definition: StackPanel.cs:339
Orientation
Defines the different orientations that a control or layout can have.
Definition: Orientation.cs:8
override Vector3 MeasureOverride(Vector3 availableSizeWithoutMargins)
When overridden in a derived class, measures the size in layout required for possible child elements ...
Definition: StackPanel.cs:185
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
void ScrollToBeginning(Orientation direction)
Go to the beginning of the element in the given the direction.
Definition: StackPanel.cs:571
override Vector3 ArrangeOverride(Vector3 finalSizeWithoutMargins)
When overridden in a derived class, positions possible child elements and determines a size for a UIE...
Definition: StackPanel.cs:225
void ScrollToEnd()
Scroll to the end of the StackPanel.
Definition: StackPanel.cs:566
void ScrollToPreviousPage(Orientation direction)
Go to the previous page in the given the direction.
Definition: StackPanel.cs:548
Arranges child elements into a single line that can be oriented horizontally or vertically.
Definition: StackPanel.cs:15
Represents the main scrollable region inside a ScrollViewer control.
Definition: IScrollInfo.cs:11
override void OnLogicalChildRemoved(UIElement oldElement, int index)
Action to perform when a logical child is removed.
Definition: StackPanel.cs:169
void ScrollToNextPage(Orientation direction)
Go to the next page in the given the direction.
Definition: StackPanel.cs:543
bool CanScroll(Orientation direction)
Gets a value that indicates if the UIElement can scroll in the provided direction.
Definition: StackPanel.cs:328
override void OnLogicalChildAdded(UIElement newElement, int index)
Action to perform when a logical child is added.
Definition: StackPanel.cs:177
Android.Widget.Orientation Orientation
Definition: Section.cs:9
A class that represents a tag propety.
Definition: PropertyKey.cs:17
void ScrollToPreviousPage()
Scroll to the previous page of elements of the StackPanel.
Definition: StackPanel.cs:538
void ScrollToPreviousLine(Orientation direction)
Go to the previous line in the given the direction.
Definition: StackPanel.cs:504
void ScrolllToElement(float elementIndex)
Jump to the element having the provided index.
Definition: StackPanel.cs:365
void ScrollToPreviousLine()
Scroll to the previous element of the StackPanel.
Definition: StackPanel.cs:494
Provides a base class for all Panel elements. Use Panel elements to position and arrange child object...
Definition: Panel.cs:19