File: MS\Internal\Media\VisualTreeUtils.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
 
namespace MS.Internal.Media
{
    /// <summary>
    ///     Helper methods for interacting with the 2D/3D Visual tree
    /// </summary>
    internal static class VisualTreeUtils
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
        
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
        
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------        
 
        #region Internal Methods
 
        /// <summary>
        ///     This walks up the Visual tree setting the given flags starting at the
        ///     given element.
        /// </summary>
        internal static void PropagateFlags(
            DependencyObject element, 
            VisualFlags flags,
            VisualProxyFlags proxyFlags)
        {
            Visual visual;
            Visual3D visual3D;
            
            AsVisualInternal(element, out visual, out visual3D);
 
            if (visual != null)
            {
                Visual.PropagateFlags(visual, flags, proxyFlags);
            }
            else
            {
                Visual3D.PropagateFlags(visual3D, flags, proxyFlags);
            }
        }
 
        /// <summary>
        ///     Walks up the Visual tree setting or clearing the given flags.  Unlike
        ///     PropagateFlags this does not terminate when it reaches node with
        ///     the flags already set.  It always walks all the way to the root.
        /// </summary>
        internal static void SetFlagsToRoot(DependencyObject element, bool value, VisualFlags flags)
        {
            Visual visual;
            Visual3D visual3D;
            
            AsVisualInternal(element, out visual, out visual3D);
 
            if (visual != null)
            {
                visual.SetFlagsToRoot(value, flags);
            }
            else if (visual3D != null)
            {
                visual3D.SetFlagsToRoot(value, flags);
            }
        }
 
        /// <summary>
        ///     Finds the first ancestor of the given element which has the given
        ///     flags set.
        /// </summary>
        internal static DependencyObject FindFirstAncestorWithFlagsAnd(DependencyObject element, VisualFlags flags)
        {
            Visual visual;
            Visual3D visual3D;
            
            AsVisualInternal(element, out visual, out visual3D);
 
            if (visual != null)
            {
                return visual.FindFirstAncestorWithFlagsAnd(flags);
            }
            else if (visual3D != null)
            {
                return visual3D.FindFirstAncestorWithFlagsAnd(flags);
            }
 
            Debug.Assert(element == null);
 
            return null;
        }
 
        /// <summary>
        ///     Converts the given point or ray hit test result into a PointHitTestResult.
        ///     In the case of a RayHitTestResult this is done by walking up the
        ///     transforming the 3D intersection into the coordinate space of the
        ///     Viewport3DVisual which contains the Visual3D subtree.
        /// </summary>
        internal static PointHitTestResult AsNearestPointHitTestResult(HitTestResult result)
        {
            if (result == null)
            {
                return null;
            }
            
            PointHitTestResult resultAsPointHitTestResult = result as PointHitTestResult;
 
            if (resultAsPointHitTestResult != null)
            {
                return resultAsPointHitTestResult;
            }
 
            RayHitTestResult resultAsRayHitTestResult = result as RayHitTestResult;
 
            if (resultAsRayHitTestResult != null)
            {
                Visual3D current = (Visual3D) resultAsRayHitTestResult.VisualHit;
                Matrix3D worldTransform = Matrix3D.Identity;
 
                while (true)
                {
                    if (current.Transform != null)
                    {
                        worldTransform.Append(current.Transform.Value);
                    }
 
                    Visual3D parent3D = current.InternalVisualParent as Visual3D;
 
                    if (parent3D == null)
                    {
                        break;
                    }
 
                    current = parent3D;
                }
 
                Viewport3DVisual viewport = current.InternalVisualParent as Viewport3DVisual;
 
                if (viewport != null)
                {
                    Point4D worldPoint = ((Point4D)resultAsRayHitTestResult.PointHit) * worldTransform;
                    Point viewportPoint = viewport.WorldToViewport(worldPoint);
                    
                    return new PointHitTestResult(viewport, viewportPoint);
                }
 
                Debug.Fail("How did a ray hit a Visual3D not parented to a Viewport3DVisual?");
 
                return null;
            }
 
            Debug.Fail(String.Format("Unhandled HitTestResult type '{0}'", result.GetType().Name));
 
            return null;
        }
 
        /// <summary>
        ///     Throws if the given element is null or is not a Visual or Visual3D
        ///     or if the Visual is on the wrong thread.
        /// </summary>
        internal static void EnsureNonNullVisual(DependencyObject element)
        {
            EnsureVisual(element, /* allowNull = */ false);
        }
 
        /// <summary>
        ///     Throws if the given element is null or is not a Visual or Visual3D
        ///     or if the Visual is on the wrong thread.
        /// </summary>
        internal static void EnsureVisual(DependencyObject element)
        {
            EnsureVisual(element, /* allowNull = */ true);
        }
 
        /// <summary>
        ///     Throws if the given element is not a Visual or Visual3D
        ///     or if the Visual is on the wrong thread.
        /// </summary>
        private static void EnsureVisual(DependencyObject element, bool allowNull)
        {
            if (element == null)
            {
                if (!allowNull)
                {
                    throw new ArgumentNullException("element");
                }
 
                return;
            }
 
            // Would DTypes be faster?
            if (!(element is Visual || element is Visual3D))
            {
                throw new ArgumentException(SR.Visual_NotAVisual);
            }
 
            element.VerifyAccess();            
        }
 
        /// <summary>
        ///     Throws if the given element is null or not a visual type, otherwise
        ///     either visual or visual3D will be non-null on exit.
        /// </summary>
        internal static void AsNonNullVisual(DependencyObject element, out Visual visual, out Visual3D visual3D)
        {
            ArgumentNullException.ThrowIfNull(element);
 
            AsVisual(element, out visual, out visual3D);
 
            Debug.Assert((visual == null) != (visual3D == null),
                "Either visual or visual3D exclusively should be non-null.");
        }
        
        /// <summary>
        ///     Returns null if the given element is null, otherwise visual or visual3D
        ///     will be the strong visual type on exit.
        ///
        ///     Throws an exception if element is a non-Visual type.
        /// </summary>
        internal static void AsVisual(DependencyObject element, out Visual visual, out Visual3D visual3D)
        {
            bool castSucceeded = AsVisualHelper(element, out visual, out visual3D);
 
            if (element != null)
            {
                if (!castSucceeded)
                {
                    throw new System.InvalidOperationException(SR.Format(SR.Visual_NotAVisual, element.GetType()));
                }
 
                Debug.Assert(castSucceeded && ((visual == null) != (visual3D == null)),
                    "Either visual or visual3D exclusively should be non-null.");
 
                element.VerifyAccess();
            }
            else
            {
                Debug.Assert(!castSucceeded && visual == null && visual3D == null,
                    "How did the cast succeed if the element was null going in?");
            }
        }
 
        /// <summary>
        ///     Internal helper to cast to DO to Visual or Visual3D.  Asserts
        ///     that the DO is really a Visual type.  Null is allowed.  Does not
        ///     VerifyAccess.
        ///
        ///     Caller is responsible for guaranteeing that element is a Visual type.
        /// </summary>
        internal static bool AsVisualInternal(DependencyObject element, out Visual visual, out Visual3D visual3D)
        {
            bool castSucceeded = AsVisualHelper(element, out visual, out visual3D);
 
            if (!(castSucceeded || element == null))
            {
                Debug.Fail(String.Format(
                               "'{0}' is not a Visual or Visual3D. Caller is responsible for guaranteeing that element is a Visual type.",
                               element != null ? element.GetType() : null));
            }
 
            return castSucceeded;
        }
        
        #endregion Internal Methods        
 
        //------------------------------------------------------
        //
        //  Internal Fields
        //
        //------------------------------------------------------
        
        #region Internal Fields
 
        public const string BitmapEffectObsoleteMessage = 
            "BitmapEffects are deprecated and no longer function.  Consider using Effects where appropriate instead.";
 
        #endregion
            
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
        
        #region Private Methods
 
        // Common code for AsVisual and AsVisualInternal -- Don't call this.
        private static bool AsVisualHelper(DependencyObject element, out Visual visual, out Visual3D visual3D)
        {
            Visual elementAsVisual = element as Visual;
 
            if (elementAsVisual != null)
            {
                visual = elementAsVisual;
                visual3D = null;
                return true;
            }
 
            Visual3D elementAsVisual3D = element as Visual3D;
 
            if (elementAsVisual3D != null)
            {
                visual = null;
                visual3D = elementAsVisual3D;
                return true;
            }            
            
            visual = null;
            visual3D = null;
            return false;
        }
 
        #endregion Private Methods        
    }
}