// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: Contains the ScrollContentPresenter class. // using MS.Internal; using MS.Utility; using System.ComponentModel; using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Media; namespace System.Windows.Controls { /// <summary> /// </summary> public sealed class ScrollContentPresenter : ContentPresenter, IScrollInfo { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors /// <summary> /// Default DependencyObject constructor /// </summary> /// <remarks> /// Automatic determination of current Dispatcher. Use alternative constructor /// that accepts a Dispatcher for best performance. /// </remarks> public ScrollContentPresenter() : base() { _adornerLayer = new AdornerLayer(); } #endregion //------------------------------------------------------------------- // // Public Methods // //------------------------------------------------------------------- #region Public Methods /// <summary> /// Scroll content by one line to the top. /// </summary> public void LineUp() { if (IsScrollClient) { SetVerticalOffset(VerticalOffset - ScrollViewer._scrollLineDelta); } } /// <summary> /// Scroll content by one line to the bottom. /// </summary> public void LineDown() { if (IsScrollClient) { SetVerticalOffset(VerticalOffset + ScrollViewer._scrollLineDelta); } } /// <summary> /// Scroll content by one line to the left. /// </summary> public void LineLeft() { if (IsScrollClient) { SetHorizontalOffset(HorizontalOffset - ScrollViewer._scrollLineDelta); } } /// <summary> /// Scroll content by one line to the right. /// </summary> public void LineRight() { if (IsScrollClient) { SetHorizontalOffset(HorizontalOffset + ScrollViewer._scrollLineDelta); } } /// <summary> /// Scroll content by one page to the top. /// </summary> public void PageUp() { if (IsScrollClient) { SetVerticalOffset(VerticalOffset - ViewportHeight); } } /// <summary> /// Scroll content by one page to the bottom. /// </summary> public void PageDown() { if (IsScrollClient) { SetVerticalOffset(VerticalOffset + ViewportHeight); } } /// <summary> /// Scroll content by one page to the left. /// </summary> public void PageLeft() { if (IsScrollClient) { SetHorizontalOffset(HorizontalOffset - ViewportWidth); } } /// <summary> /// Scroll content by one page to the right. /// </summary> public void PageRight() { if (IsScrollClient) { SetHorizontalOffset(HorizontalOffset + ViewportWidth); } } /// <summary> /// Scroll content by one line to the top. /// </summary> public void MouseWheelUp() { if (IsScrollClient) { SetVerticalOffset(VerticalOffset - ScrollViewer._mouseWheelDelta); } } /// <summary> /// Scroll content by one line to the bottom. /// </summary> public void MouseWheelDown() { if (IsScrollClient) { SetVerticalOffset(VerticalOffset + ScrollViewer._mouseWheelDelta); } } /// <summary> /// Scroll content by one page to the top. /// </summary> public void MouseWheelLeft() { if (IsScrollClient) { SetHorizontalOffset(HorizontalOffset - ScrollViewer._mouseWheelDelta); } } /// <summary> /// Scroll content by one page to the bottom. /// </summary> public void MouseWheelRight() { if (IsScrollClient) { SetHorizontalOffset(HorizontalOffset + ScrollViewer._mouseWheelDelta); } } /// <summary> /// Set the HorizontalOffset to the passed value. /// </summary> public void SetHorizontalOffset(double offset) { if (IsScrollClient) { double newValue = ValidateInputOffset(offset, "HorizontalOffset"); if (!DoubleUtil.AreClose(EnsureScrollData()._offset.X, newValue)) { _scrollData._offset.X = newValue; InvalidateArrange(); } } } /// <summary> /// Set the VerticalOffset to the passed value. /// </summary> public void SetVerticalOffset(double offset) { if (IsScrollClient) { double newValue = ValidateInputOffset(offset, "VerticalOffset"); if (!DoubleUtil.AreClose(EnsureScrollData()._offset.Y, newValue)) { _scrollData._offset.Y = newValue; InvalidateArrange(); } } } /// <summary> /// ScrollContentPresenter implementation of <seealso cref="IScrollInfo.MakeVisible" />. /// </summary> public Rect MakeVisible(Visual visual, Rect rectangle) { return MakeVisible(visual, rectangle, true); } #endregion //------------------------------------------------------------------- // // Public Properties (CLR + Avalon) // //------------------------------------------------------------------- #region Public Properties /// <summary> /// AdornerLayer on which adorners are rendered. /// Adorners are rendered under the ScrollContentPresenter's clip region. /// </summary> public AdornerLayer AdornerLayer { get { return _adornerLayer; } } /// <summary> /// This property indicates whether the ScrollContentPresenter should try to allow the Content /// to scroll or not. A true value indicates Content should be allowed to scroll if it supports /// IScrollInfo. A false value will cause ScrollContentPresenter to always act as the scrolling /// client. /// </summary> public bool CanContentScroll { get { return (bool) GetValue(CanContentScrollProperty); } set { SetValue(CanContentScrollProperty, value); } } /// <summary> /// ScrollContentPresenter reacts to this property by changing it's child measurement algorithm. /// If scrolling in a dimension, infinite space is allowed the child; otherwise, available size is preserved. /// </summary> public bool CanHorizontallyScroll { get { return (IsScrollClient) ? EnsureScrollData()._canHorizontallyScroll : false; } set { if (IsScrollClient && (EnsureScrollData()._canHorizontallyScroll != value)) { _scrollData._canHorizontallyScroll = value; InvalidateMeasure(); } } } /// <summary> /// ScrollContentPresenter reacts to this property by changing it's child measurement algorithm. /// If scrolling in a dimension, infinite space is allowed the child; otherwise, available size is preserved. /// </summary> public bool CanVerticallyScroll { get { return (IsScrollClient) ? EnsureScrollData()._canVerticallyScroll : false; } set { if (IsScrollClient && (EnsureScrollData()._canVerticallyScroll != value)) { _scrollData._canVerticallyScroll = value; InvalidateMeasure(); } } } /// <summary> /// ExtentWidth contains the horizontal size of the scrolled content element in 1/96" /// </summary> public double ExtentWidth { get { return (IsScrollClient) ? EnsureScrollData()._extent.Width : 0.0; } } /// <summary> /// ExtentHeight contains the vertical size of the scrolled content element in 1/96" /// </summary> public double ExtentHeight { get { return (IsScrollClient) ? EnsureScrollData()._extent.Height : 0.0; } } /// <summary> /// ViewportWidth contains the horizontal size of content's visible range in 1/96" /// </summary> public double ViewportWidth { get { return (IsScrollClient) ? EnsureScrollData()._viewport.Width : 0.0; } } /// <summary> /// ViewportHeight contains the vertical size of content's visible range in 1/96" /// </summary> public double ViewportHeight { get { return (IsScrollClient) ? EnsureScrollData()._viewport.Height : 0.0; } } /// <summary> /// HorizontalOffset is the horizontal offset of the scrolled content in 1/96". /// </summary> [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public double HorizontalOffset { get { return (IsScrollClient) ? EnsureScrollData()._computedOffset.X : 0.0; } } /// <summary> /// VerticalOffset is the vertical offset of the scrolled content in 1/96". /// </summary> [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public double VerticalOffset { get { return (IsScrollClient) ? EnsureScrollData()._computedOffset.Y : 0.0; } } /// <summary> /// ScrollOwner is the container that controls any scrollbars, headers, etc... that are dependant /// on this ScrollArea's properties. /// </summary> [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ScrollViewer ScrollOwner { get { return (IsScrollClient) ? _scrollData._scrollOwner: null; } set { if (IsScrollClient) { _scrollData._scrollOwner = value; } } } /// <summary> /// DependencyProperty for <see cref="CanContentScroll" /> property. /// </summary> public static readonly DependencyProperty CanContentScrollProperty = ScrollViewer.CanContentScrollProperty.AddOwner( typeof(ScrollContentPresenter), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnCanContentScrollChanged))); #endregion //------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// <summary> /// Returns the Visual children count. /// </summary> protected override int VisualChildrenCount { get { // Four states make sense: // 0 Children. No Content or AdornerLayer. Valid - do nothing. // 2 Children. Content is first child, AdornerLayer // One for the base.TemplateChild and one for the _adornerlayer. return (base.TemplateChild == null) ? 0 : 2; } } /// <summary> /// Returns the child at the specified index. /// </summary> protected override Visual GetVisualChild(int index) { //check if there is a TemplateChild on FrameworkElement if (base.TemplateChild == null) { throw new ArgumentOutOfRangeException(nameof(index), index, SR.Visual_ArgumentOutOfRange); } else { switch (index) { case 0: return base.TemplateChild; case 1: return _adornerLayer; default: throw new ArgumentOutOfRangeException(nameof(index), index, SR.Visual_ArgumentOutOfRange); } } } /// <summary> /// Gets or sets the template child of the FrameworkElement. /// </summary> internal override UIElement TemplateChild { get { return base.TemplateChild; } set { UIElement oldTemplate = base.TemplateChild; if (value != oldTemplate) { if (oldTemplate != null && value == null) { // If we used to have a template child and we don't have a // new template child disconnect the adorner layer. this.RemoveVisualChild(_adornerLayer); } base.TemplateChild = value; if(oldTemplate == null && value != null) { // If we did not use to have a template child, but we have one // now, attach the adorner layer. this.AddVisualChild(_adornerLayer); } } } } /// <summary> /// </summary> protected override Size MeasureOverride(Size constraint) { Size desiredSize = new Size(); bool etwTracingEnabled = IsScrollClient && EventTrace.IsEnabled(EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info); if (etwTracingEnabled) { EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientStringBegin, EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info, "SCROLLCONTENTPRESENTER:MeasureOverride"); } try { int count = this.VisualChildrenCount; if (count > 0) { // The AdornerLayer is always the size of our surface, and does not contribute to our own size. _adornerLayer.Measure(constraint); if (!IsScrollClient) { desiredSize = base.MeasureOverride(constraint); } else { Size childConstraint = constraint; if (_scrollData._canHorizontallyScroll) { childConstraint.Width = Double.PositiveInfinity; } if (_scrollData._canVerticallyScroll) { childConstraint.Height = Double.PositiveInfinity; } desiredSize = base.MeasureOverride(childConstraint); } } // If we're handling scrolling (as the physical scrolling client, validate properties. if (IsScrollClient) { VerifyScrollData(constraint, desiredSize); } desiredSize.Width = Math.Min(constraint.Width, desiredSize.Width); desiredSize.Height = Math.Min(constraint.Height, desiredSize.Height); } finally { if (etwTracingEnabled) { EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientStringEnd, EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info, "SCROLLCONTENTPRESENTER:MeasureOverride"); } } return desiredSize; } /// <summary> /// </summary> protected override Size ArrangeOverride(Size arrangeSize) { bool etwTracingEnabled = IsScrollClient && EventTrace.IsEnabled(EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info); if (etwTracingEnabled) { EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientStringBegin, EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info, "SCROLLCONTENTPRESENTER:ArrangeOverride"); } try { int count = this.VisualChildrenCount; // Verifies IScrollInfo properties & invalidates ScrollViewer if necessary. if (IsScrollClient) { VerifyScrollData(arrangeSize, _scrollData._extent); } if (count > 0) { _adornerLayer.Arrange(new Rect(arrangeSize)); UIElement child = this.GetVisualChild(0) as UIElement; if (child != null) { Rect childRect = new Rect(child.DesiredSize); if (IsScrollClient) { childRect.X = -HorizontalOffset; childRect.Y = -VerticalOffset; } //this is needed to stretch the child to arrange space, childRect.Width = Math.Max(childRect.Width, arrangeSize.Width); childRect.Height = Math.Max(childRect.Height, arrangeSize.Height); child.Arrange(childRect); } } } finally { if (etwTracingEnabled) { EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientStringEnd, EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info, "SCROLLCONTENTPRESENTER:ArrangeOverride"); } } return (arrangeSize); } /// <summary> /// Override of <seealso cref="UIElement.GetLayoutClip"/>. /// </summary> /// <returns>Viewport geometry</returns> protected override Geometry GetLayoutClip(Size layoutSlotSize) { return new RectangleGeometry(new Rect(RenderSize)); } /// <summary> /// Called when the Template's tree has been generated /// </summary> public override void OnApplyTemplate() { base.OnApplyTemplate(); // Add the AdornerLayer to our visual tree. // Iff we have content, we need an adorner layer. // It has Content(eg. Button, TextBlock) as its first child and AdornerLayer as its second child // Get our scrolling owner and content talking. HookupScrollingComponents(); } #endregion //------------------------------------------------------------------- // // Internal Methods // //------------------------------------------------------------------- #region Internal Methods /// <summary> /// ScrollContentPresenter implementation of <seealso cref="IScrollInfo.MakeVisible" />. /// </summary> /// <param name="visual">The Visual that should become visible</param> /// <param name="rectangle">A rectangle representing in the visual's coordinate space to make visible.</param> /// <param name="throwOnError">If true the method throws an exception when an error is encountered, otherwise the method returns Rect.Empty when an error is encountered</param> /// <returns> /// A rectangle in the IScrollInfo's coordinate space that has been made visible. /// Other ancestors to in turn make this new rectangle visible. /// The rectangle should generally be a transformed version of the input rectangle. In some cases, like /// when the input rectangle cannot entirely fit in the viewport, the return value might be smaller. /// </returns> internal Rect MakeVisible(Visual visual, Rect rectangle, bool throwOnError) { // (ScrollContentPresenter.MakeVisible can cause an exception when encountering an empty rectangle) // This method exists to keep ScrollContentPresenter.MakeVisible v1 behavior // while allowing callers of IScrollInfo.MakeVisible in the platform work around a bug // in the v1 behavior. // If this bug is fixed look for callers of IScrollInfo.MakeVisible with workarounds. // They should be updated remove the workarounds. // // Note: This code presently assumes we/children are layout clean. See work item 22269 for more detail. // // We can only work on visuals that are us or children. // An empty rect has no size or position. We can't meaningfully use it. if (rectangle.IsEmpty || visual == null || visual == (Visual)this || !this.IsAncestorOf(visual)) { return Rect.Empty; } // Compute the child's rect relative to (0,0) in our coordinate space. GeneralTransform childTransform = visual.TransformToAncestor(this); rectangle = childTransform.TransformBounds(rectangle); if (!IsScrollClient || (!throwOnError && rectangle.IsEmpty)) { return rectangle; } // Initialize the viewport Rect viewport = new Rect(HorizontalOffset, VerticalOffset, ViewportWidth, ViewportHeight); rectangle.X += viewport.X; rectangle.Y += viewport.Y; // Compute the offsets required to minimally scroll the child maximally into view. double minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right); double minY = ComputeScrollOffsetWithMinimalScroll(viewport.Top, viewport.Bottom, rectangle.Top, rectangle.Bottom); // We have computed the scrolling offsets; scroll to them. SetHorizontalOffset(minX); SetVerticalOffset(minY); // Compute the visible rectangle of the child relative to the viewport. viewport.X = minX; viewport.Y = minY; rectangle.Intersect(viewport); if (throwOnError) { // (ScrollContentPresenter.MakeVisible can cause an exception when encountering an empty rectangle) // Old behavior for app compat rectangle.X -= viewport.X; rectangle.Y -= viewport.Y; } else { // (ScrollContentPresenter.MakeVisible can cause an exception when encountering an empty rectangle) // New correct behavior if (!rectangle.IsEmpty) { rectangle.X -= viewport.X; rectangle.Y -= viewport.Y; } } // Return the rectangle return rectangle; } internal static double ComputeScrollOffsetWithMinimalScroll( double topView, double bottomView, double topChild, double bottomChild) { bool alignTop = false; bool alignBottom = false; return ComputeScrollOffsetWithMinimalScroll(topView, bottomView, topChild, bottomChild, ref alignTop, ref alignBottom); } internal static double ComputeScrollOffsetWithMinimalScroll( double topView, double bottomView, double topChild, double bottomChild, ref bool alignTop, ref bool alignBottom) { // # CHILD POSITION CHILD SIZE SCROLL REMEDY // 1 Above viewport <= viewport Down Align top edge of child & viewport // 2 Above viewport > viewport Down Align bottom edge of child & viewport // 3 Below viewport <= viewport Up Align bottom edge of child & viewport // 4 Below viewport > viewport Up Align top edge of child & viewport // 5 Entirely within viewport NA No scroll. // 6 Spanning viewport NA No scroll. // // Note: "Above viewport" = childTop above viewportTop, childBottom above viewportBottom // "Below viewport" = childTop below viewportTop, childBottom below viewportBottom // These child thus may overlap with the viewport, but will scroll the same direction/ bool fAbove = DoubleUtil.LessThan(topChild, topView) && DoubleUtil.LessThan(bottomChild, bottomView); bool fBelow = DoubleUtil.GreaterThan(bottomChild, bottomView) && DoubleUtil.GreaterThan(topChild, topView); bool fLarger = (bottomChild - topChild) > (bottomView - topView); // Handle Cases: 1 & 4 above if ((fAbove && !fLarger) || (fBelow && fLarger) || alignTop) { alignTop = true; return topChild; } // Handle Cases: 2 & 3 above else if (fAbove || fBelow || alignBottom) { alignBottom = true; return (bottomChild - (bottomView - topView)); } // Handle cases: 5 & 6 above. return topView; } internal static double ValidateInputOffset(double offset, string parameterName) { if (double.IsNaN(offset)) { throw new ArgumentOutOfRangeException(parameterName, SR.Format(SR.ScrollViewer_CannotBeNaN, parameterName)); } return Math.Max(0.0, offset); } #endregion //------------------------------------------------------------------- // // Private Methods // //------------------------------------------------------------------- #region Private Methods private ScrollData EnsureScrollData() { if (_scrollData == null) { _scrollData = new ScrollData(); } return _scrollData; } // Helper method to get our ScrollViewer owner and its scrolling content talking. // Method introduces the current owner/content, and clears a from any previous content. internal void HookupScrollingComponents() { // We need to introduce our IScrollInfo to our ScrollViewer (and break any previous links). ScrollViewer scrollContainer = TemplatedParent as ScrollViewer; // If our content is not an IScrollInfo, we should have selected a style that contains one. // This (readonly) style contains an AdornerDecorator with a ScrollArea child. if (scrollContainer != null) { IScrollInfo si = null; if (CanContentScroll) { // We need to get an IScrollInfo to introduce to the ScrollViewer. // 1. Try our content... si = Content as IScrollInfo; if (si == null) { Visual child = Content as Visual; if (child != null) { // 2. Our child might be an ItemsPresenter. In this case check its child for being an IScrollInfo ItemsPresenter itemsPresenter = child as ItemsPresenter; if (itemsPresenter == null) { // 3. With the change in templates for ClearTypeHint the ItemsPresenter is not guranteed to be the // immediate child. We now look for a named element instead of naively walking the descendents. FrameworkElement templatedParent = scrollContainer.TemplatedParent as FrameworkElement; if (templatedParent != null) { itemsPresenter = templatedParent.GetTemplateChild("ItemsPresenter") as ItemsPresenter; } } if (itemsPresenter != null) { itemsPresenter.ApplyTemplate(); int count = VisualTreeHelper.GetChildrenCount(itemsPresenter); if(count > 0) si = VisualTreeHelper.GetChild(itemsPresenter, 0) as IScrollInfo; } } } } // 4. As a final fallback, we use ourself. if (si == null) { si = (IScrollInfo)this; EnsureScrollData(); } // Detach any differing previous IScrollInfo from ScrollViewer if (si != _scrollInfo && _scrollInfo != null) { if (IsScrollClient) { _scrollData = null; } else _scrollInfo.ScrollOwner = null; } // Introduce our ScrollViewer and IScrollInfo to each other. if (si != null) { _scrollInfo = si; // At this point, we pass IsScrollClient if si == this. si.ScrollOwner = scrollContainer; scrollContainer.ScrollInfo = si; } } // We're not really in a valid scrolling scenario. Break any previous references, and get us // back into a totally unlinked state. else if (_scrollInfo != null) { _scrollInfo.ScrollOwner?.ScrollInfo = null; _scrollInfo.ScrollOwner = null; _scrollInfo = null; _scrollData = null; } } // Verifies scrolling data using the passed viewport and extent as newly computed values. // Checks the X/Y offset and coerces them into the range [0, Extent - ViewportSize] // If extent, viewport, or the newly coerced offsets are different than the existing offset, // cachces are updated and InvalidateScrollInfo() is called. private void VerifyScrollData(Size viewport, Size extent) { Debug.Assert(IsScrollClient); bool fValid = true; // These two lines of code are questionable, but they are needed right now as VSB may return // Infinity size from measure, which is a regression from the old scrolling model. // They also have the incidental affect of probably avoiding reinvalidation at Arrange // when inside a parent that measures you to Infinity. if (Double.IsInfinity(viewport.Width)) viewport.Width = extent.Width; if (Double.IsInfinity(viewport.Height)) viewport.Height = extent.Height; fValid &= DoubleUtil.AreClose(viewport, _scrollData._viewport); fValid &= DoubleUtil.AreClose(extent, _scrollData._extent); _scrollData._viewport = viewport; _scrollData._extent = extent; fValid &= CoerceOffsets(); if (!fValid) { ScrollOwner.InvalidateScrollInfo(); } } // Returns an offset coerced into the [0, Extent - Viewport] range. // Internal because it is also used by other Avalon ISI implementations (just to avoid code duplication). internal static double CoerceOffset(double offset, double extent, double viewport) { if (offset > extent - viewport) { offset = extent - viewport; } if (offset < 0) { offset = 0; } return offset; } private bool CoerceOffsets() { Debug.Assert(IsScrollClient); Vector computedOffset = new Vector( CoerceOffset(_scrollData._offset.X, _scrollData._extent.Width, _scrollData._viewport.Width), CoerceOffset(_scrollData._offset.Y, _scrollData._extent.Height, _scrollData._viewport.Height)); bool fValid = DoubleUtil.AreClose(_scrollData._computedOffset, computedOffset); _scrollData._computedOffset = computedOffset; return fValid; } // This property is structurally important; we can't do layout without it set right. // So, we synchronously make changes. private static void OnCanContentScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ScrollContentPresenter scp = (ScrollContentPresenter)d; if (scp._scrollInfo == null) { return; } // the code that was here appeared to care about the first time this property was ever set -- verify if this replacement is okay scp.HookupScrollingComponents(); scp.InvalidateMeasure(); } #endregion //------------------------------------------------------------------- // // Private Properties // //------------------------------------------------------------------- #region Private Properties private bool IsScrollClient { get { return (_scrollInfo == this); } } // // This property // 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject // 2. This is a performance optimization // internal override int EffectiveValuesInitialSize { get { return 42; } } #endregion //------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------- #region Private Fields // Only one of the following will be used. // The _scrollInfo holds a content IScrollInfo implementation that is given to the ScrollViewer. // _scrollData holds values for the scrolling properties we use if we are handling IScrollInfo for the ScrollViewer ourself. // ScrollData could implement IScrollInfo, but then the v-table would hurt in the common case as much as we save // in the less common case. private IScrollInfo _scrollInfo; private ScrollData _scrollData; // To hold adorners (caret, &c...) under the clipping region of the scroller. private readonly AdornerLayer _adornerLayer; #endregion //------------------------------------------------------ // // Private Structures / Classes // //------------------------------------------------------ #region Private Structures Classes //----------------------------------------------------------- // ScrollData class //----------------------------------------------------------- #region ScrollData // Helper class to hold scrolling data. // This class exists to reduce working set when SCP is delegating to another implementation of ISI. // Standard "extra pointer always for less data sometimes" cache savings model: // Consider using the Stack internal helper. It's more or less the same. private class ScrollData { internal ScrollViewer _scrollOwner; internal bool _canHorizontallyScroll; internal bool _canVerticallyScroll; internal Vector _offset; // Set scroll offset of content. Positive corresponds to a visually upward offset. internal Vector _computedOffset; // Actual (computed) scroll offset of content. "" "" internal Size _viewport; // ViewportSize is computed from our FinalSize, but may be in different units. internal Size _extent; // Extent is the total size of our content. } #endregion ScrollData #endregion Private Structures Classes } }