|
// 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.
// Description:
// Defines a node in the composition scene graph.
using System.Windows.Threading;
using System.Windows.Diagnostics;
using System.Windows.Media.Media3D;
using System.Windows.Media.Composition;
using System.Windows.Media.Effects;
using System.Windows.Interop;
using MS.Internal;
using MS.Internal.Media;
using MS.Internal.Media3D;
//------------------------------------------------------------------------------
// This section lists various things that we could improve on the Visual class.
//
// - (Pail) Don't allocate a managed Pail object.
// - (Finalizer) Currently we delete the cob explicitly when a node is removed from
// the scene graph. However, we don't do this when we remove the root node.
// currently this is done by the finalizer. If we clean explicitly up when we
// remove the root node we won't need a finalizer.
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// PUBLIC API EXPOSURE RULES
//
// If you expose a public/protected API you need to check a couple things:
//
// A) Call the correct version of VerifyAPI. This checks the following
// 1) That the calling thread has entered the context of this object
// 2) That the current object is not disposed.
// 3) If another object is passed in, that it has the same
// context affinity as this object. This should be used for
// arguments to the API
// 4) That the current permissions are acceptable.
//
// B) That other arguments are not disposed if needed.
//
//
//------------------------------------------------------------------------------
namespace System.Windows.Media
{
// this class is used to wrap the Map struct into an object so
// that we can use it with the UncommonField infrastructure.
internal class MapClass
{
internal MapClass()
{
_map_ofBrushes = new DUCE.Map<bool>();
}
internal bool IsEmpty
{
get
{
return _map_ofBrushes.IsEmpty();
}
}
public DUCE.Map<bool> _map_ofBrushes;
}
/// <summary>
/// The Visual class is the base class for all Visual types. It provides
/// services and properties that all Visuals have in common. Services include
/// hit-testing, coordinate transformation, bounding box calculations. Properties
/// are for example a transform property and an opacity property.
///
/// Derived Visuals render their content first and then render the children, or in other
/// words, the content of a Visual is always behind the content of its children.
/// </summary>
public abstract partial class Visual : DependencyObject, DUCE.IResource
{
// --------------------------------------------------------------------
//
// Constants
//
// --------------------------------------------------------------------
#region Constants
/// <summary>
/// This is the dirty mask for a visual, set every time we marshall
/// a visual to a channel and reset by the end of the render pass.
/// </summary>
private const VisualProxyFlags c_ProxyFlagsDirtyMask =
VisualProxyFlags.IsSubtreeDirtyForRender
| VisualProxyFlags.IsContentDirty
| VisualProxyFlags.IsTransformDirty
| VisualProxyFlags.IsGuidelineCollectionDirty
| VisualProxyFlags.IsClipDirty
| VisualProxyFlags.IsOpacityDirty
| VisualProxyFlags.IsOpacityMaskDirty
| VisualProxyFlags.IsOffsetDirty
| VisualProxyFlags.IsEdgeModeDirty
| VisualProxyFlags.IsEffectDirty
| VisualProxyFlags.IsBitmapScalingModeDirty
| VisualProxyFlags.IsScrollableAreaClipDirty
| VisualProxyFlags.IsClearTypeHintDirty
| VisualProxyFlags.IsCacheModeDirty
| VisualProxyFlags.IsTextRenderingModeDirty
| VisualProxyFlags.IsTextHintingModeDirty;
/// <summary>
/// This is the dirty mask for a visual, set every time we marshall
/// a visual to a channel and reset by the end of the render pass.
///
/// This mask is only for Viewport3D visual, since the contents
/// of the Viewport3D are rendered during RenderContent, we
/// need to set those flags as dirty if the visual is not on
/// channel yet
/// </summary>
private const VisualProxyFlags c_Viewport3DProxyFlagsDirtyMask =
VisualProxyFlags.Viewport3DVisual_IsCameraDirty
| VisualProxyFlags.Viewport3DVisual_IsViewportDirty;
#endregion Constants
// --------------------------------------------------------------------
//
// Internal Constructor
//
// --------------------------------------------------------------------
#region Internal Constructor
/// <summary>
/// This internal ctor is a hook to allow Visual subclasses
/// to create their unique type of a visual resource.
/// </summary>
internal Visual(DUCE.ResourceType resourceType)
{
#if DEBUG
_parentIndex = -1;
#endif
switch (resourceType)
{
case DUCE.ResourceType.TYPE_VISUAL:
// Default setting
break;
case DUCE.ResourceType.TYPE_VIEWPORT3DVISUAL:
SetFlags(true, VisualFlags.IsViewport3DVisual);
break;
default:
Debug.Assert(false, "TYPE_VISUAL or TYPE_VIEWPORT3DVISUAL expected.");
break;
}
}
#endregion Protected Constructor
// --------------------------------------------------------------------
//
// Protected Constructor
//
// --------------------------------------------------------------------
#region Protected Constructor
/// <summary>
/// Ctor Visual
/// </summary>
protected Visual() : this(DUCE.ResourceType.TYPE_VISUAL)
{
}
#endregion Protected Constructor
// --------------------------------------------------------------------
//
// IResource implementation
//
// --------------------------------------------------------------------
#region IResource implementation
/// <summary>
/// This is used to check if the composition node
/// for the visual is on channel
/// </summary>
/// <param name="channel"></param>
/// <returns></returns>
internal bool IsOnChannel(DUCE.Channel channel)
{
return _proxy.IsOnChannel(channel);
}
/// <summary>
/// Returns the handle this visual has on the given channel.
/// </summary>
/// <param name="channel"></param>
/// <returns></returns>
DUCE.ResourceHandle DUCE.IResource.GetHandle(DUCE.Channel channel)
{
return _proxy.GetHandle(channel);
}
/// <summary>
/// Only Viewport3DVisual and Visual3D implements this.
/// Vieport3DVisual has two handles. One stored in _proxy
/// and the other one stored in _proxy3D. This function returns
/// the handle stored in _proxy3D.
/// </summary>
DUCE.ResourceHandle DUCE.IResource.Get3DHandle(DUCE.Channel channel)
{
throw new NotImplementedException();
}
/// <summary>
/// This is used to create or addref the visual resource
/// on the given channel
/// </summary>
/// <param name="channel"></param>
/// <returns></returns>
DUCE.ResourceHandle DUCE.IResource.AddRefOnChannel(DUCE.Channel channel)
{
return AddRefOnChannelCore(channel);
}
internal virtual DUCE.ResourceHandle AddRefOnChannelCore(DUCE.Channel channel)
{
DUCE.ResourceType resourceType = DUCE.ResourceType.TYPE_VISUAL;
if (CheckFlagsAnd(VisualFlags.IsViewport3DVisual))
{
resourceType = DUCE.ResourceType.TYPE_VIEWPORT3DVISUAL;
}
_proxy.CreateOrAddRefOnChannel(this, channel, resourceType);
return _proxy.GetHandle(channel);
}
/// <summary>
/// this is used to release the comp node of the visual
/// on the given channel
/// </summary>
/// <param name="channel"></param>
/// <returns></returns>
internal virtual void ReleaseOnChannelCore(DUCE.Channel channel)
{
_proxy.ReleaseOnChannel(channel);
}
/// <summary>
/// Sends a command to compositor to remove the child
/// from its parent on the channel.
/// </summary>
void DUCE.IResource.RemoveChildFromParent(
DUCE.IResource parent,
DUCE.Channel channel)
{
DUCE.CompositionNode.RemoveChild(
parent.GetHandle(channel),
_proxy.GetHandle(channel),
channel);
}
int DUCE.IResource.GetChannelCount()
{
return _proxy.Count;
}
DUCE.Channel DUCE.IResource.GetChannel(int index)
{
return _proxy.GetChannel(index);
}
#endregion IResource implementation
// --------------------------------------------------------------------
//
// IElement implementation
//
// --------------------------------------------------------------------
// --------------------------------------------------------------------
//
// Internal Properties
//
// --------------------------------------------------------------------
#region Internal Properties
// Are we in the process of iterating the visual children.
// This flag is set during a descendents walk, for property invalidation.
internal bool IsVisualChildrenIterationInProgress
{
get { return CheckFlagsAnd(VisualFlags.IsVisualChildrenIterationInProgress); }
set { SetFlags(value, VisualFlags.IsVisualChildrenIterationInProgress); }
}
/// <summary>
/// The CompositionTarget marks the root element. The root element is responsible
/// for posting renders.
/// </summary>
/// <remarks>
/// The property getter is also used to ensure that the Visual is
/// not used in multiple CompositionTargets.
/// </remarks>
internal bool IsRootElement
{
get
{
return CheckFlagsAnd(VisualFlags.ShouldPostRender);
}
set
{
SetFlags(value, VisualFlags.ShouldPostRender);
}
}
#endregion Internal Properties
// --------------------------------------------------------------------
//
// Visual Content
//
// --------------------------------------------------------------------
#region Visual Content
/// <summary>
/// Derived classes must override this method and return the bounding
/// box of their content.
/// </summary>
internal virtual Rect GetContentBounds()
{
return Rect.Empty;
}
/// <summary>
/// RenderContent is implemented by derived classes to hook up their
/// content. The implementer of this function can assert that the
/// visual is marshaled on the current channel when the function
/// is executed.
/// </summary>
internal virtual void RenderContent(RenderContext ctx, bool isOnChannel)
{
/* do nothing */
}
/// <summary>
/// This method is overrided on Visuals that can instantiate IDrawingContext
/// Currently, DrawingVisual and UIElement
/// </summary>
internal virtual void RenderClose(IDrawingContent newContent)
{
}
/// <summary>
/// VisualContentBounds returns the bounding box for the contents of the current visual.
/// </summary>
internal Rect VisualContentBounds
{
get
{
// Probably too restrictive. Let's see who wants it during OnRender.
VerifyAPIReadWrite();
return GetContentBounds();
}
}
/// <summary>
/// VisualDescendantBounds returns the union of all of the content bounding
/// boxes for all of the descendants of the current visual,and also including
/// the contents of the current visual. So we end up with the
/// bounds of the whole sub-graph in inner space.
/// </summary>
internal Rect VisualDescendantBounds
{
get
{
// Probably too restrictive. Let's see who wants it during OnRender.
VerifyAPIReadWrite();
Rect bboxSubgraph = CalculateSubgraphBoundsInnerSpace();
// If bounding box has NaN, then we set the bounding box to infinity.
if (DoubleUtil.RectHasNaN(bboxSubgraph))
{
bboxSubgraph.X = Double.NegativeInfinity;
bboxSubgraph.Y = Double.NegativeInfinity;
bboxSubgraph.Width = Double.PositiveInfinity;
bboxSubgraph.Height = Double.PositiveInfinity;
}
return bboxSubgraph;
}
}
/// <summary>
/// Computes the union of all content bounding boxes of this Visual's sub-graph
/// in inner space. Note that the result includes the root Visual's content.
///
/// Definition: Outer/Inner Space:
///
/// A Visual has a set of properties which include clip, transform, offset
/// and bitmap effect. Those properties affect in which space (coordinate
/// clip space) a Visual's vector graphics and sub-graph is interpreted.
/// Inner space is the space before applying any of the properties. Outer
/// space is the space where all the properties have been taken into account.
/// For example if the Visual draws a rectangle {0, 0, 100, 100} and the
/// Offset property is set to {20, 20} and the clip is set to {40, 40, 10, 10}
/// then the bounding box of the Visual in inner space is {0, 0, 100, 100} and
/// in outer space {60, 60, 10, 10} (start out with the bbox of {0, 0, 100, 100}
/// then apply the clip {40, 40, 10, 10} which leaves us with a bbox of
/// {40, 40, 10, 10} and finally apply the offset and we end up with a bbox
/// of {60, 60, 10, 10}
/// </summary>
internal Rect CalculateSubgraphBoundsInnerSpace()
{
return CalculateSubgraphBoundsInnerSpace(false);
}
/// <summary>
/// Computes the union of all rendering bounding boxes of this Visual's sub-graph
/// in inner space. Note that the result includes the root Visual's content.
/// </summary>
internal Rect CalculateSubgraphRenderBoundsInnerSpace()
{
return CalculateSubgraphBoundsInnerSpace(true);
}
/// <summary>
/// Same as the parameterless CalculateSubgraphBoundsInnerSpace except it takes a
/// boolean indicating whether or not to calculate the rendering bounds.
/// If the renderBounds parameter is set to true then the render bounds are returned.
/// The render bounds differ in that they treat zero area bounds as emtpy rectangles.
///
///
/// This is needed since MIL and the managed size differ about how big content bounds are
/// WPF considers geometric bounds (i.e. it will union in points) while MIL considers anything
/// with zero area to be empty. This poses problems when looking for the exact size of a
/// CyclicBrush.
/// </summary>
internal virtual Rect CalculateSubgraphBoundsInnerSpace(bool renderBounds)
{
Rect bboxSubgraph = Rect.Empty;
// Recursively calculate sub-graph bounds of children of this node. We get
// the bounds of each child Visual in outer space which is this Visual's
// inner space and union them together.
int count = VisualChildrenCount;
for (int i = 0; i < count; i++)
{
Visual child = GetVisualChild(i);
if (child != null)
{
Rect bboxSubgraphChild = child.CalculateSubgraphBoundsOuterSpace(renderBounds);
bboxSubgraph.Union(bboxSubgraphChild);
}
}
// Get the content bounds of the Visual. In the case that we're interested in render
// bounds (i.e. what MIL will consider the size of the object), we set 0 area rects
// to be empty so that they don't union to create larger sized rects.
Rect contentBounds = GetContentBounds();
if (renderBounds && IsEmptyRenderBounds(ref contentBounds /* ref for perf - not modified */))
{
contentBounds = Rect.Empty;
}
// Union the content bounds to the sub-graph bounds so that we end up with the
// bounds of the whole sub-graph in inner space and return it.
bboxSubgraph.Union(contentBounds);
return bboxSubgraph;
}
/// <summary>
/// Computes the union of all content bounding boxes of this Visual's sub-graph
/// in outer space. Note that the result includes the root Visual's content.
/// For a definition of outer/inner space see CalculateSubgraphBoundsInnerSpace.
/// </summary>
internal Rect CalculateSubgraphBoundsOuterSpace()
{
return CalculateSubgraphBoundsOuterSpace(false /* renderBounds */);
}
/// <summary>
/// Computes the union of all rendering bounding boxes of this Visual's sub-graph
/// in outer space. Note that the result includes the root Visual's content.
/// For a definition of outer/inner space see CalculateSubgraphBoundsInnerSpace.
/// </summary>
internal Rect CalculateSubgraphRenderBoundsOuterSpace()
{
return CalculateSubgraphBoundsOuterSpace(true /* renderBounds */);
}
/// <summary>
/// Same as the parameterless CalculateSubgraphBoundsOuterSpace except it takes a
/// boolean indicating whether or not to calculate the rendering bounds.
/// If the renderBounds parameter is set to true then the render bounds are returned.
/// The render bounds differ in that they treat zero area bounds as emtpy rectangles.
///
///
/// This is needed since MIL and the managed size differ about how big content bounds are
/// WPF considers geometric bounds (i.e. it will union in points) while MIL considers anything
/// with zero area to be empty. This poses problems when looking for the exact size of a
/// CyclicBrush.
/// </summary>
private Rect CalculateSubgraphBoundsOuterSpace(bool renderBounds)
{
Rect bboxSubgraph = Rect.Empty;
// Get the inner space bounding box of this node and then transform it into outer
// space.
bboxSubgraph = CalculateSubgraphBoundsInnerSpace(renderBounds);
// Apply Effect
if (CheckFlagsAnd(VisualFlags.NodeHasEffect))
{
Rect effectBounds;
Effect effect = EffectField.GetValue(this);
if (effect != null)
{
// The Effect always deals in unit bounds, so transform the
// unit rect and then map back into the world space bounds
// defined by bboxSubgraph.
Rect unitBounds = new Rect(0,0,1,1);
Rect unitTransformedBounds = effect.EffectMapping.TransformBounds(unitBounds);
effectBounds = Effect.UnitToWorld(unitTransformedBounds, bboxSubgraph);
bboxSubgraph.Union(effectBounds);
}
else
{
Debug.Assert(BitmapEffectStateField.GetValue(this) != null);
// BitmapEffects are deprecated so they no longer affect bounds.
}
}
// Apply Clip.
Geometry clip = ClipField.GetValue(this);
if (clip != null)
{
bboxSubgraph.Intersect(clip.Bounds);
}
// Apply Transform.
Transform transform = TransformField.GetValue(this);
if ((transform != null) && (!transform.IsIdentity))
{
Matrix m = transform.Value;
MatrixUtil.TransformRect(ref bboxSubgraph, ref m);
}
// Apply Offset.
if (!bboxSubgraph.IsEmpty)
{
bboxSubgraph.X += _offset.X;
bboxSubgraph.Y += _offset.Y;
}
// Apply scrollable-area clip.
Rect? scrollClip = ScrollableAreaClipField.GetValue(this);
if (scrollClip.HasValue)
{
bboxSubgraph.Intersect(scrollClip.Value);
}
// If bounding box has NaN, then we set the bounding box to infinity.
if (DoubleUtil.RectHasNaN(bboxSubgraph))
{
bboxSubgraph.X = Double.NegativeInfinity;
bboxSubgraph.Y = Double.NegativeInfinity;
bboxSubgraph.Width = Double.PositiveInfinity;
bboxSubgraph.Height = Double.PositiveInfinity;
}
return bboxSubgraph;
}
/// <summary>
/// This method returns true if the given WPF bounds will be considered
/// empty in terms of rendering. This is the case when the bounds describe
/// a zero-area space. bounds are passed by ref for speed but are not modified
///
///
/// See above CalculateSubgraphBounds* methods for more detail. This helper method
/// goes with them.
/// </summary>
private bool IsEmptyRenderBounds(ref Rect bounds)
{
return (bounds.Width <= 0 || bounds.Height <= 0);
}
#endregion Visual Content
// --------------------------------------------------------------------
//
// Resource Marshalling and Unmarshalling
//
// --------------------------------------------------------------------
#region Resource Marshalling and Unmarshalling
/// <summary>
/// Override this function in derived classes to release unmanaged resources during Dispose
/// and during removal of a subtree.
/// </summary>
internal virtual void FreeContent(DUCE.Channel channel)
{
Debug.Assert(IsOnChannel(channel));
Debug.Assert(!CheckFlagsAnd(channel, VisualProxyFlags.IsContentNodeConnected));
}
/// <summary>
/// Returns true if this is a root of a ICyclicBrush on the specified channel
/// </summary>
private bool IsCyclicBrushRootOnChannel(DUCE.Channel channel)
{
bool isCyclicBrushRootOnChannel = false;
Dictionary<DUCE.Channel, int> channelsToCyclicBrushMap =
ChannelsToCyclicBrushMapField.GetValue(this);
if (channelsToCyclicBrushMap != null)
{
int references;
if (channelsToCyclicBrushMap.TryGetValue(channel, out references))
{
isCyclicBrushRootOnChannel = (references > 0);
}
}
return isCyclicBrushRootOnChannel;
}
/// <summary>
/// Frees up resources in this visual's subtree.
/// </summary>
/// <param name="channel">
/// The channel to release the resources on.
/// </param>
void DUCE.IResource.ReleaseOnChannel(DUCE.Channel channel)
{
if (!IsOnChannel(channel)
|| CheckFlagsAnd(channel, VisualProxyFlags.IsDeleteResourceInProgress))
{
return;
}
// Set the flag to true to prevent re-entrancy.
SetFlags(channel, true, VisualProxyFlags.IsDeleteResourceInProgress);
try
{
// at this point the tree is not connected any more.
SetFlags(channel, false, VisualProxyFlags.IsConnectedToParent);
//
// Before unmarshaling this visual and its subtree, check if there are any visual
// brushes holding references to it. In such case, we want to keep this visual
// in the marshaled state and wait for all the visual brushes to release their
// references through ReleaseOnChannelForCyclicBrush.
//
//
// RenderTargetBitmap and BitmapEffects use synchronous channels. If a
// node on the synchronous channel is the root of a VisualBrush from another
// channel, then the node will never be deleted. Instead we really need to
// check if the node is the root of a VisualBrush _on the same channel_.
// This check is more expensive so we'll leave the faster check first to avoid
// the more expensive check which isn't necessary most of the time.
//
// If the node is the root of a VisualBrush and the VisualBrush is one of
// the node's children then a cycle is created. All of the nodes on the
// cycle will leak. On sync channels, this is particularly bad because
// the user doesn't know about the sync channel and has no control over it.
// We have a queue of sync channels that are reused and leaking can lead
// to conflicts on channel reuse resulting in a crash.
//
// *** DANGER *** Fortunately, as of today, tree structure on a sync channel is
// never manipulated. The tree gets built, the tree gets drawn, and the tree gets
// released. Because of this, we can just always delete. In the future if that
// changes, the isSynchronous check here will cause a problem. *** DANGER ***
//
if ( !CheckFlagsOr(VisualFlags.NodeIsCyclicBrushRoot)
// If we aren't a root of a CyclicBrush, then we aren't referenced
// at all and we can go away
|| !channel.IsConnected
// If the channel isn't connected, there's no reason to keep things alive
|| channel.IsSynchronous
// If the channel is synchronous, the node isn't going to stick around
// so just delete it. *** THIS IS DANGEROUS ***. See above for
// more comments.
|| !IsCyclicBrushRootOnChannel(channel)
// If we got to here, we are the root of a VisualBrush. We can go away
// only if the VB is on a different channel. This check is more expensive
// and not very common so we put it last.
)
{
FreeContent(channel);
// Free dependent DUCE resources.
//
// We don't need to free the dependent resources if they're
// marked as dirty because when the flag is set, we also
// disconnect the resource from the visual resource.
Transform transform = TransformField.GetValue(this);
if ((transform != null)
&& (!CheckFlagsAnd(channel, VisualProxyFlags.IsTransformDirty)))
{
//
// Note that in this particular case, the transform is not
// really dirty. Namely because the visual is not marshalled.
//
((DUCE.IResource)transform).ReleaseOnChannel(channel);
}
Effect effect = EffectField.GetValue(this);
if ((effect != null)
&& (!CheckFlagsAnd(channel, VisualProxyFlags.IsEffectDirty)))
{
((DUCE.IResource)effect).ReleaseOnChannel(channel);
}
Geometry clip = ClipField.GetValue(this);
if ((clip != null)
&& (!CheckFlagsAnd(channel, VisualProxyFlags.IsClipDirty)))
{
((DUCE.IResource)clip).ReleaseOnChannel(channel);
}
Brush opacityMask = OpacityMaskField.GetValue(this);
if ((opacityMask != null)
&& (!CheckFlagsAnd(channel, VisualProxyFlags.IsOpacityMaskDirty)))
{
((DUCE.IResource)opacityMask).ReleaseOnChannel(channel);
}
CacheMode cacheMode = CacheModeField.GetValue(this);
if ((cacheMode != null)
&& (! CheckFlagsAnd(channel, VisualProxyFlags.IsCacheModeDirty)))
{
((DUCE.IResource)cacheMode).ReleaseOnChannel(channel);
}
//
// Release the visual.
//
this.ReleaseOnChannelCore(channel);
//
// Finally, the children.
//
int count = VisualChildrenCount;
for (int i = 0; i < count; i++)
{
Visual visual = GetVisualChild(i);
if (visual != null)
{
((DUCE.IResource)visual).ReleaseOnChannel(channel);
}
}
}
}
finally
{
//
// We need to reset this flag if we are still on channel since we
// have only decreased the ref-count and not deleted the resource.
//
if (IsOnChannel(channel))
{
SetFlags(channel, false, VisualProxyFlags.IsDeleteResourceInProgress);
}
}
}
internal virtual void AddRefOnChannelForCyclicBrush(
ICyclicBrush cyclicBrush,
DUCE.Channel channel)
{
//
// Since the ICyclicBrush to visual relationship is being created on this channel,
// we need to update the number of cyclic brushes using this visual on this channel.
//
Dictionary<DUCE.Channel, int> channelsToCyclicBrushMap =
ChannelsToCyclicBrushMapField.GetValue(this);
if (channelsToCyclicBrushMap == null)
{
channelsToCyclicBrushMap = new Dictionary<DUCE.Channel, int>();
ChannelsToCyclicBrushMapField.SetValue(this, channelsToCyclicBrushMap);
}
if (!channelsToCyclicBrushMap.ContainsKey(channel))
{
// If on this channel we were not previously using this Visual as the root
// node of a VisualBrush, set the flag indicating that it is the root now.
// Also set the number of uses on this channel to 1.
SetFlags(true, VisualFlags.NodeIsCyclicBrushRoot);
channelsToCyclicBrushMap[channel] = 1;
}
else
{
Debug.Assert(channelsToCyclicBrushMap[channel] > 0);
channelsToCyclicBrushMap[channel] += 1;
}
//
// Since the ICyclicBrush to visual relationship is being created on this channel,
// we need to update the number of times this cyclic brush is used across all
// channels.
//
Dictionary<ICyclicBrush, int> cyclicBrushToChannelsMap =
CyclicBrushToChannelsMapField.GetValue(this);
if (cyclicBrushToChannelsMap == null)
{
cyclicBrushToChannelsMap = new Dictionary<ICyclicBrush, int>();
CyclicBrushToChannelsMapField.SetValue(this, cyclicBrushToChannelsMap);
}
if (!cyclicBrushToChannelsMap.ContainsKey(cyclicBrush))
{
cyclicBrushToChannelsMap[cyclicBrush] = 1;
}
else
{
Debug.Assert(cyclicBrushToChannelsMap[cyclicBrush] > 0);
cyclicBrushToChannelsMap[cyclicBrush] += 1;
}
//
// Render the brush's visual.
//
cyclicBrush.RenderForCyclicBrush(channel, false);
}
/// <summary>
/// Override this function in derived classes to release unmanaged resources
/// during Dispose and during removal of a subtree.
/// </summary>
internal virtual void ReleaseOnChannelForCyclicBrush(
ICyclicBrush cyclicBrush,
DUCE.Channel channel)
{
// Update the number of times this visual brush uses this visual across all channels.
Dictionary<ICyclicBrush, int> cyclicBrushToChannelsMap =
CyclicBrushToChannelsMapField.GetValue(this);
Debug.Assert(cyclicBrushToChannelsMap != null);
Debug.Assert(cyclicBrushToChannelsMap.ContainsKey(cyclicBrush));
Debug.Assert(cyclicBrushToChannelsMap[cyclicBrush] > 0);
if (cyclicBrushToChannelsMap[cyclicBrush] == 1)
{
//
// If the ICyclicBrush no longer uses this Visual across all channels, then
// we can remove it from the map.
//
cyclicBrushToChannelsMap.Remove(cyclicBrush);
}
else
{
// Decrease the number os times this ICyclicBrush uses this Visual across all channels
cyclicBrushToChannelsMap[cyclicBrush] =
cyclicBrushToChannelsMap[cyclicBrush] - 1;
}
// Decrease the number of ICyclicBrush using the visual as root on this channel
Dictionary<DUCE.Channel, int> channelsToCyclicBrushMap =
ChannelsToCyclicBrushMapField.GetValue(this);
Debug.Assert(channelsToCyclicBrushMap != null);
Debug.Assert(channelsToCyclicBrushMap.ContainsKey(channel));
Debug.Assert(channelsToCyclicBrushMap[channel] > 0);
channelsToCyclicBrushMap[channel] =
channelsToCyclicBrushMap[channel] - 1;
//
// If on this channel, there are no more ICyclicBrushes using this visual as
// a root then we need to remove the flag saying that the visual is a visual
// brush root and make sure that the dependant resources are released in
// case we are no longer connected to the visual tree.
//
if (channelsToCyclicBrushMap[channel] == 0)
{
channelsToCyclicBrushMap.Remove(channel);
SetFlags(false, VisualFlags.NodeIsCyclicBrushRoot);
PropagateFlags(
this,
VisualFlags.None,
VisualProxyFlags.IsSubtreeDirtyForRender);
//
// If we do not have a parent or we have already disconnected from
// the parent and we are also not the root then we need to clear out
// the tree.
//
if ( (_parent == null
|| !CheckFlagsAnd(channel, VisualProxyFlags.IsConnectedToParent))
&& !IsRootElement)
{
((DUCE.IResource)this).ReleaseOnChannel(channel);
}
}
}
#endregion Resource Marshalling and Unmarshalling
// --------------------------------------------------------------------
//
// Access Verification
//
// --------------------------------------------------------------------
#region Access Verification
/// <summary>
/// Applies various API checks
/// </summary>
internal void VerifyAPIReadOnly()
{
// Verify that we are executing on the right context
VerifyAccess();
}
/// <summary>
/// Applies various API checks
/// </summary>
internal void VerifyAPIReadOnly(DependencyObject value)
{
VerifyAPIReadOnly();
// Make sure the value is on the same context as the visual.
// AssertSameContext handles null and Dispatcher-free values.
MediaSystem.AssertSameContext(this, value);
}
/// <summary>
/// Applies various API checks for read/write
/// </summary>
internal void VerifyAPIReadWrite()
{
VerifyAPIReadOnly();
// Verify the correct access permissions
MediaContext.From(this.Dispatcher).VerifyWriteAccess();
}
/// <summary>
/// Applies various API checks
/// </summary>
internal void VerifyAPIReadWrite(DependencyObject value)
{
VerifyAPIReadWrite();
// Make sure the value is on the same context as the visual.
// AssertSameContext handles null and Dispatcher-free values.
MediaSystem.AssertSameContext(this, value);
}
#endregion Access Verification
// --------------------------------------------------------------------
//
// Pre-compute / render passes
//
// --------------------------------------------------------------------
#region Pre-compute / render passes
internal void Precompute()
{
if (CheckFlagsAnd(VisualFlags.IsSubtreeDirtyForPrecompute))
{
// Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
using(Dispatcher.DisableProcessing())
{
MediaContext mediaContext = MediaContext.From(Dispatcher);
try
{
mediaContext.PushReadOnlyAccess();
Rect bboxSubgraph;
PrecomputeRecursive(out bboxSubgraph);
}
finally
{
mediaContext.PopReadOnlyAccess();
}
}
}
}
/// <summary>
/// Derived class can do precomputations on their content by overriding this method.
/// Derived classes must call the base class.
/// </summary>
internal virtual void PrecomputeContent()
{
_bboxSubgraph = GetHitTestBounds();
// If bounding box has NaN, then we set the bounding box to infinity.
if (DoubleUtil.RectHasNaN(_bboxSubgraph))
{
_bboxSubgraph.X = Double.NegativeInfinity;
_bboxSubgraph.Y = Double.NegativeInfinity;
_bboxSubgraph.Width = Double.PositiveInfinity;
_bboxSubgraph.Height = Double.PositiveInfinity;
}
}
internal void PrecomputeRecursive(out Rect bboxSubgraph)
{
// Simple loop detection to avoid stack overflow in cyclic Visual
// scenarios. This fix is only aimed at mitigating a very common
// VisualBrush scenario.
bool canEnter = Enter();
if (canEnter)
{
try
{
if (CheckFlagsAnd(VisualFlags.IsSubtreeDirtyForPrecompute))
{
PrecomputeContent();
int childCount = VisualChildrenCount;
for (int i = 0; i < childCount; i++)
{
Visual child = GetVisualChild(i);
if (child != null)
{
Rect bboxSubgraphChild;
child.PrecomputeRecursive(out bboxSubgraphChild);
_bboxSubgraph.Union(bboxSubgraphChild);
}
}
SetFlags(false, VisualFlags.IsSubtreeDirtyForPrecompute);
}
// Bounding boxes are cached in inner space (below offset, transform, and clip).
// Before returning them we need
// to transform them into outer space.
bboxSubgraph = _bboxSubgraph;
Geometry clip = ClipField.GetValue(this);
if (clip != null)
{
bboxSubgraph.Intersect(clip.Bounds);
}
Transform transform = TransformField.GetValue(this);
if ((transform != null) && (!transform.IsIdentity))
{
Matrix m = transform.Value;
MatrixUtil.TransformRect(ref bboxSubgraph, ref m);
}
if (!bboxSubgraph.IsEmpty)
{
bboxSubgraph.X += _offset.X;
bboxSubgraph.Y += _offset.Y;
}
Rect? scrollClip = ScrollableAreaClipField.GetValue(this);
if (scrollClip.HasValue)
{
bboxSubgraph.Intersect(scrollClip.Value);
}
// If child's bounding box has NaN, then we set the bounding box to infinity.
if (DoubleUtil.RectHasNaN(bboxSubgraph))
{
bboxSubgraph.X = Double.NegativeInfinity;
bboxSubgraph.Y = Double.NegativeInfinity;
bboxSubgraph.Width = Double.PositiveInfinity;
bboxSubgraph.Height = Double.PositiveInfinity;
}
}
finally
{
Exit();
}
}
else
{
bboxSubgraph = new Rect();
}
}
internal void Render(RenderContext ctx, UInt32 childIndex)
{
DUCE.Channel channel = ctx.Channel;
//
// Currently everything is sent to the compositor. IsSubtreeDirtyForRender
// indicates that something in the sub-graph of this Visual needs to have an update
// sent to the compositor. Hence traverse if this bit is set. Also traverse when the
// sub-graph has not yet been sent to the compositor.
//
if (CheckFlagsAnd(channel, VisualProxyFlags.IsSubtreeDirtyForRender)
|| !IsOnChannel(channel))
{
RenderRecursive(ctx);
}
//
// Connect the root visual to the composition root if necessary.
//
if (IsOnChannel(channel)
&& !CheckFlagsAnd(channel, VisualProxyFlags.IsConnectedToParent)
&& !ctx.Root.IsNull)
{
DUCE.CompositionNode.InsertChildAt(
ctx.Root,
_proxy.GetHandle(channel),
childIndex,
channel);
SetFlags(
channel,
true,
VisualProxyFlags.IsConnectedToParent);
}
}
internal virtual void RenderRecursive(
RenderContext ctx)
{
// Simple loop detection to avoid stack overflow in cyclic Visual
// scenarios. This fix is only aimed at mitigating a very common
// VisualBrush scenario.
bool canEnter = Enter();
if (canEnter)
{
try
{
DUCE.Channel channel = ctx.Channel;
DUCE.ResourceHandle handle = DUCE.ResourceHandle.Null;
VisualProxyFlags flags = VisualProxyFlags.None;
//
// See if this visual is already on that channel
//
bool isOnChannel = IsOnChannel(channel);
//
// Ensure that the visual resource for this Visual
// is being sent to our current channel.
//
if (isOnChannel)
{
//
// Good, we're already on channel. Get the handle and flags.
//
handle = _proxy.GetHandle(channel);
flags = _proxy.GetFlags(channel);
}
else
{
//
// Create the visual resource on the current channel.
//
// Need to update all set properties.
//
handle = ((DUCE.IResource)this).AddRefOnChannel(channel);
// we need to set the Viewport3D flags, if the visual is not
// on channel so that the viewport sends all its resources
// to the compositor. we need the explicit set, because
// the update happens during RenderContent and we have no
// other way to pass the flags
//
// We do that for all visuals. the flags will be ignored
// if the visual is not a Viewport3D visual
SetFlags(channel, true, c_Viewport3DProxyFlagsDirtyMask);
flags = c_ProxyFlagsDirtyMask;
}
UpdateCacheMode(channel, handle, flags, isOnChannel);
UpdateTransform(channel, handle, flags, isOnChannel);
UpdateClip(channel, handle, flags, isOnChannel);
UpdateOffset(channel, handle, flags, isOnChannel);
UpdateEffect(channel, handle, flags, isOnChannel);
UpdateGuidelines(channel, handle, flags, isOnChannel);
UpdateContent(ctx, flags, isOnChannel);
UpdateOpacity(channel, handle, flags, isOnChannel);
UpdateOpacityMask(channel, handle, flags, isOnChannel);
UpdateRenderOptions(channel, handle, flags, isOnChannel);
UpdateChildren(ctx, handle);
UpdateScrollableAreaClip(channel, handle, flags, isOnChannel);
//
// Finally, reset the dirty flags for this visual (at this point,
// we have handled them all).
SetFlags(channel, false, VisualProxyFlags.IsSubtreeDirtyForRender);
}
finally
{
Exit();
}
}
}
/// <summary>
/// Enter is used for simple cycle detection in Visual. If the method returns false
/// the Visual has already been entered and cannot be entered again. Matching invocation of Exit
/// must be skipped if Enter returns false.
/// </summary>
internal bool Enter()
{
if (CheckFlagsAnd(VisualFlags.ReentrancyFlag))
{
return false;
}
else
{
SetFlags(true, VisualFlags.ReentrancyFlag);
return true;
}
}
/// <summary>
/// Exits the Visual. For more details see Enter method.
/// </summary>
internal void Exit()
{
Debug.Assert(CheckFlagsAnd(VisualFlags.ReentrancyFlag)); // Exit must be matched with Enter. See Enter comments.
SetFlags(false, VisualFlags.ReentrancyFlag);
}
/// <summary>
/// Update opacity
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel"></param>
private void UpdateOpacity(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Opacity ----------------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsOpacityDirty) != 0)
{
double opacity = OpacityField.GetValue(this);
if (isOnChannel || !(opacity >= 1.0))
{
//
// Opacity is 1.0 by default -- do not send it for new visuals.
//
DUCE.CompositionNode.SetAlpha(
handle,
opacity,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsOpacityDirty);
}
}
/// <summary>
/// Update OpacityMask
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel">The Visual exists on channel.</param>
private void UpdateOpacityMask(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Opacity Mask ----------------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsOpacityMaskDirty) != 0)
{
Brush opacityMask = OpacityMaskField.GetValue(this);
if (opacityMask != null)
{
//
// Set the new opacity mask resource on the visual.
// If opacityMask is null we don't need to do this.
// Also note that the old opacity mask was disconnected
// in the OpacityMask property setter.
//
DUCE.CompositionNode.SetAlphaMask(
handle,
((DUCE.IResource)opacityMask).AddRefOnChannel(channel),
channel);
}
else if (isOnChannel) /* opacityMask == null */
{
DUCE.CompositionNode.SetAlphaMask(
handle,
DUCE.ResourceHandle.Null,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsOpacityMaskDirty);
}
}
/// <summary>
/// Update transform
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel">The Visual exists on channel.</param>
private void UpdateTransform(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Transform -------------------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsTransformDirty) != 0)
{
Transform transform = TransformField.GetValue(this);
if (transform != null)
{
//
// Set the new transform resource on the visual.
// If transform is null we don't need to do this.
// Also note that the old transform was disconnected
// in the Transform property setter.
//
DUCE.CompositionNode.SetTransform(
handle,
((DUCE.IResource)transform).AddRefOnChannel(channel),
channel);
}
else if (isOnChannel) /* transform == null */
{
DUCE.CompositionNode.SetTransform(
handle,
DUCE.ResourceHandle.Null,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsTransformDirty);
}
}
/// Update effect.
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel">The Visual exists on channel.</param>
private void UpdateEffect(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Effect -------------------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsEffectDirty) != 0)
{
Effect effect = EffectField.GetValue(this);
if (effect != null)
{
//
// Set the new effect resource on the visual.
// If effect is null we don't need to do this.
// Also note that the old effect was disconnected
// in the Effect property setter.
//
DUCE.CompositionNode.SetEffect(
handle,
((DUCE.IResource)effect).AddRefOnChannel(channel),
channel);
}
else if (isOnChannel) /* effect == null */
{
DUCE.CompositionNode.SetEffect(
handle,
DUCE.ResourceHandle.Null,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsEffectDirty);
}
}
/// <summary>
/// Update cache mode.
/// </summary>
private void UpdateCacheMode(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Cache Mode -------------------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsCacheModeDirty) != 0)
{
CacheMode cacheMode = CacheModeField.GetValue(this);
if (cacheMode != null)
{
//
// Set the new cache mode resource on the visual.
// If cacheMode is null we don't need to do this.
// Also note that the old cache mode was disconnected
// in the CacheMode property setter.
//
DUCE.CompositionNode.SetCacheMode(
handle,
((DUCE.IResource)cacheMode).AddRefOnChannel(channel),
channel);
}
else if (isOnChannel) /* cacheMode == null */
{
DUCE.CompositionNode.SetCacheMode(
handle,
DUCE.ResourceHandle.Null,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsCacheModeDirty);
}
}
/// <summary>
/// Update clip
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel">The Visual exists on channel.</param>
private void UpdateClip(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Clip ------------------------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsClipDirty) != 0)
{
Geometry clip = ClipField.GetValue(this);
if (clip != null)
{
//
// Set the new clip resource on the composition node.
// If clip is null we don't need to do this. Also note
// that the old clip was disconnected in the Clip
// property setter.
//
DUCE.CompositionNode.SetClip(
handle,
((DUCE.IResource)clip).AddRefOnChannel(channel),
channel);
}
else if (isOnChannel) /* clip == null */
{
DUCE.CompositionNode.SetClip(
handle,
DUCE.ResourceHandle.Null,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsClipDirty);
}
}
/// <summary>
/// Update scrollable area clip
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel">The Visual exists on channel.</param>
private void UpdateScrollableAreaClip(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
if ((flags & VisualProxyFlags.IsScrollableAreaClipDirty) != 0)
{
Rect? scrollableArea = ScrollableAreaClipField.GetValue(this);
if (isOnChannel || (scrollableArea != null))
{
DUCE.CompositionNode.SetScrollableAreaClip(
handle,
scrollableArea,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsScrollableAreaClipDirty);
}
}
/// <summary>
/// Update offset
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel"></param>
private void UpdateOffset(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Offset --------------------------------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsOffsetDirty) != 0)
{
if (isOnChannel || _offset != new Vector())
{
//
// Offset is (0, 0) by default so do not update it for new visuals.
//
DUCE.CompositionNode.SetOffset(
handle,
_offset.X,
_offset.Y,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsOffsetDirty);
}
}
/// <summary>
/// Update guidelines
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel"></param>
private void UpdateGuidelines(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
// Guidelines --------------------------------------------------------------------
if ((flags & VisualProxyFlags.IsGuidelineCollectionDirty) != 0)
{
DoubleCollection guidelinesX = GuidelinesXField.GetValue(this);
DoubleCollection guidelinesY = GuidelinesYField.GetValue(this);
if (isOnChannel || (guidelinesX != null || guidelinesY != null))
{
//
// Guidelines are null by default, so do not update them for new visuals.
//
DUCE.CompositionNode.SetGuidelineCollection(
handle,
guidelinesX,
guidelinesY,
channel);
}
SetFlags(channel, false, VisualProxyFlags.IsGuidelineCollectionDirty);
}
}
/// <summary>
/// Update EdgeMode
/// </summary>
/// <param name="channel"></param>
/// <param name="handle"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel"></param>
private void UpdateRenderOptions(DUCE.Channel channel,
DUCE.ResourceHandle handle,
VisualProxyFlags flags,
bool isOnChannel)
{
if (((flags & VisualProxyFlags.IsEdgeModeDirty) != 0) ||
((flags & VisualProxyFlags.IsBitmapScalingModeDirty) != 0) ||
((flags & VisualProxyFlags.IsClearTypeHintDirty) != 0) ||
((flags & VisualProxyFlags.IsTextRenderingModeDirty) != 0) ||
((flags & VisualProxyFlags.IsTextHintingModeDirty) != 0))
{
MilRenderOptions renderOptions = new MilRenderOptions();
// EdgeMode ----------------------------------------------------------------------------
// "isOnChannel" (if true) indicates that this Visual was on channel
// previous to this update. If this is the case, all changes to the EdgeMode
// must be reflected in the composition node. If "isOnChannel" is false it means
// that this Visual has just been added to a channel. In this case, we can
// skip an EdgeMode update if the EdgeMode is Unspecified, as this is the default
// behavior.
EdgeMode edgeMode = EdgeModeField.GetValue(this);
if (isOnChannel || (edgeMode != EdgeMode.Unspecified))
{
renderOptions.Flags |= MilRenderOptionFlags.EdgeMode;
renderOptions.EdgeMode = edgeMode;
}
// ImageScalingMode ----------------------------------------------------------------------------
BitmapScalingMode bitmapScalingMode = BitmapScalingModeField.GetValue(this);
if (isOnChannel || (bitmapScalingMode != BitmapScalingMode.Unspecified))
{
renderOptions.Flags |= MilRenderOptionFlags.BitmapScalingMode;
renderOptions.BitmapScalingMode = bitmapScalingMode;
}
ClearTypeHint clearTypeHint = ClearTypeHintField.GetValue(this);
if (isOnChannel || (clearTypeHint != ClearTypeHint.Auto))
{
renderOptions.Flags |= MilRenderOptionFlags.ClearTypeHint;
renderOptions.ClearTypeHint = clearTypeHint;
}
TextRenderingMode textRenderingMode = TextRenderingModeField.GetValue(this);
if (isOnChannel || (textRenderingMode != TextRenderingMode.Auto))
{
renderOptions.Flags |= MilRenderOptionFlags.TextRenderingMode;
renderOptions.TextRenderingMode = textRenderingMode;
}
TextHintingMode textHintingMode = TextHintingModeField.GetValue(this);
if (isOnChannel || (textHintingMode != TextHintingMode.Auto))
{
renderOptions.Flags |= MilRenderOptionFlags.TextHintingMode;
renderOptions.TextHintingMode = textHintingMode;
}
if (renderOptions.Flags != 0)
{
DUCE.CompositionNode.SetRenderOptions(
handle,
renderOptions,
channel);
}
SetFlags(
channel,
false,
VisualProxyFlags.IsEdgeModeDirty |
VisualProxyFlags.IsBitmapScalingModeDirty |
VisualProxyFlags.IsClearTypeHintDirty |
VisualProxyFlags.IsTextRenderingModeDirty |
VisualProxyFlags.IsTextHintingModeDirty
);
}
}
/// <summary>
/// Update content
/// </summary>
/// <param name="ctx"></param>
/// <param name="flags"></param>
/// <param name="isOnChannel">The Visual exists on channel.</param>
private void UpdateContent(RenderContext ctx,
VisualProxyFlags flags,
bool isOnChannel)
{
//
// Hookup content to the Visual
//
if ((flags & VisualProxyFlags.IsContentDirty) != 0)
{
RenderContent(ctx, isOnChannel);
SetFlags(ctx.Channel, false, VisualProxyFlags.IsContentDirty);
}
}
/// <summary>
/// Update children
/// </summary>
/// <param name="ctx"></param>
/// <param name="handle"></param>
private void UpdateChildren(RenderContext ctx,
DUCE.ResourceHandle handle)
{
DUCE.Channel channel = ctx.Channel;
//
// Visit children of this visual.
//
//
// If content node is connected child node indicies need to be offset by one.
//
UInt32 connectedChildIndex =
CheckFlagsAnd(channel, VisualProxyFlags.IsContentNodeConnected) ? (UInt32)1 : 0;
bool isChildrenZOrderDirty = CheckFlagsAnd(channel, VisualProxyFlags.IsChildrenZOrderDirty);
int childCount = VisualChildrenCount;
//
// If the visual children have been re-ordered, enqueue a packet to RemoveAllChildren,
// then reinsert all the children. The parent visual will release the children when
// the RemoveAllChildren packet, but the managed visuals will still have references
// to them so that they won't be destructed and recreated.
//
if (isChildrenZOrderDirty)
{
DUCE.CompositionNode.RemoveAllChildren(
handle,
channel);
}
for (int i = 0; i < childCount; i++)
{
Visual child = GetVisualChild(i);
if (child != null)
{
//
// Recurse if the child visual is dirty
// or it has not been marshalled yet.
//
if (child.CheckFlagsAnd(channel, VisualProxyFlags.IsSubtreeDirtyForRender)
|| !(child.IsOnChannel(channel)))
{
child.RenderRecursive(ctx);
}
//
// Make sure that all the marshaled children are
// connected to the parent visual or that the ZOrder
// of the children has changed.
//
if (child.IsOnChannel(channel))
{
bool isConnectedToParent = child.CheckFlagsAnd(channel, VisualProxyFlags.IsConnectedToParent);
if (!isConnectedToParent || isChildrenZOrderDirty)
{
DUCE.CompositionNode.InsertChildAt(
handle,
((DUCE.IResource)child).GetHandle(channel),
connectedChildIndex,
channel);
child.SetFlags(
channel,
true,
VisualProxyFlags.IsConnectedToParent);
}
connectedChildIndex++;
}
}
}
SetFlags(channel, false, VisualProxyFlags.IsChildrenZOrderDirty);
}
#endregion Pre-compute / render passes
// --------------------------------------------------------------------
//
// Hit Testing
//
// --------------------------------------------------------------------
#region Hit Testing
internal class TopMostHitResult
{
internal HitTestResult _hitResult = null;
internal HitTestResultBehavior HitTestResult(HitTestResult result)
{
_hitResult = result;
return HitTestResultBehavior.Stop;
}
internal HitTestFilterBehavior NoNested2DFilter(DependencyObject potentialHitTestTarget)
{
if (potentialHitTestTarget is Viewport2DVisual3D)
{
return HitTestFilterBehavior.ContinueSkipChildren;
}
return HitTestFilterBehavior.Continue;
}
}
/// <summary>
/// Used by derived classes to invalidate their hit-test bounds.
/// </summary>
internal void InvalidateHitTestBounds()
{
VerifyAPIReadWrite();
PropagateFlags(
this,
VisualFlags.IsSubtreeDirtyForPrecompute,
VisualProxyFlags.None);
}
/// <summary>
/// Derived classes return the hit-test bounding box from the
/// GetHitTestBounds virtual. Visual uses the bounds to optimize
/// hit-testing.
/// </summary>
internal virtual Rect GetHitTestBounds()
{
return GetContentBounds();
}
/// <summary>
/// Return top most visual of a hit test.
/// </summary>
internal HitTestResult HitTest(Point point)
{
return HitTest(point, true);
}
/// <summary>
/// Return top most visual of a hit test. If include2DOn3D is true we will
/// hit test in to 2D on 3D children, otherwise we will ignore that part of
/// the tree.
/// </summary>
internal HitTestResult HitTest(Point point, bool include2DOn3D)
{
//
TopMostHitResult result = new TopMostHitResult();
VisualTreeHelper.HitTest(
this,
include2DOn3D? null : new HitTestFilterCallback(result.NoNested2DFilter),
new HitTestResultCallback(result.HitTestResult),
new PointHitTestParameters(point));
return result._hitResult;
}
/// <summary>
/// Initiate a hit test using delegates.
/// </summary>
internal void HitTest(
HitTestFilterCallback filterCallback,
HitTestResultCallback resultCallback,
HitTestParameters hitTestParameters)
{
ArgumentNullException.ThrowIfNull(resultCallback);
ArgumentNullException.ThrowIfNull(hitTestParameters);
VerifyAPIReadWrite();
Precompute();
PointHitTestParameters pointParams = hitTestParameters as PointHitTestParameters;
if (pointParams != null)
{
// Because we call dynamic code during the hit testing walk we need to back up
// the original hit point in case the user's delegate throws an exception so that
// we can restore it.
Point backupHitPoint = pointParams.HitPoint;
try
{
HitTestPoint(filterCallback, resultCallback, pointParams);
}
catch
{
// If an exception occured, restore the user's hit point and rethrow.
pointParams.SetHitPoint(backupHitPoint);
throw;
}
finally
{
Debug.Assert(Point.Equals(pointParams.HitPoint, backupHitPoint),
"Failed to restore user's hit point back to the original coordinate system.");
}
}
else
{
GeometryHitTestParameters geometryParams = hitTestParameters as GeometryHitTestParameters;
if (geometryParams != null)
{
// Because we call dynamic code during the hit testing walk we need to ensure
// that if the user's delegate throws an exception we restore the original
// transform on the hit test geometry.
#if DEBUG
// Internally we replace the hit geometry with a copy which is guaranteed to have
// a MatrixTransform so we do not need to worry about null dereferences here.
Matrix originalMatrix = geometryParams.InternalHitGeometry.Transform.Value;
#endif // DEBUG
try
{
HitTestGeometry(filterCallback, resultCallback, geometryParams);
}
catch
{
geometryParams.EmergencyRestoreOriginalTransform();
throw;
}
#if DEBUG
finally
{
Debug.Assert(Matrix.Equals(geometryParams.InternalHitGeometry.Transform.Value, originalMatrix),
"Failed to restore user's hit geometry back to the original coordinate system.");
}
#endif // DEBUG
}
else
{
// This should never happen, users can not extend the abstract HitTestParameters class.
Invariant.Assert(false,
String.Format(System.Globalization.CultureInfo.InvariantCulture,
"'{0}' HitTestParameters are not supported on {1}.",
hitTestParameters.GetType().Name, this.GetType().Name));
}
}
}
internal HitTestResultBehavior HitTestPoint(
HitTestFilterCallback filterCallback,
HitTestResultCallback resultCallback,
PointHitTestParameters pointParams)
{
// we do not need parameter checks because they are done in HitTest()
Geometry clip = VisualClip;
// Before we continue hit-testing we check against the hit-test bounds for the sub-graph.
// If the point is not with-in the hit-test bounds, the sub-graph can be skipped.
if (_bboxSubgraph.Contains(pointParams.HitPoint) &&
((null == clip) || clip.FillContains(pointParams.HitPoint))) // Check that the hit-point is with-in the clip.
{
//
// Determine if there is a special filter behavior defined for this
// Visual.
//
HitTestFilterBehavior filter = HitTestFilterBehavior.Continue;
if (filterCallback != null)
{
filter = filterCallback(this);
if (filter == HitTestFilterBehavior.ContinueSkipSelfAndChildren)
{
return HitTestResultBehavior.Continue;
}
if (filter == HitTestFilterBehavior.Stop)
{
return HitTestResultBehavior.Stop;
}
}
// if there is a bitmap effect transform the point
// Backup the hit point so that we can restore it later on.
Point originalHitPoint = pointParams.HitPoint;
Point hitPoint = originalHitPoint;
if (CheckFlagsAnd(VisualFlags.NodeHasEffect))
{
Effect imageEffect = EffectField.GetValue(this);
if (imageEffect != null)
{
GeneralTransform effectHitTestInverse = imageEffect.EffectMapping.Inverse;
// only do work if the transform isn't the identity transform
if (effectHitTestInverse != Transform.Identity)
{
bool ok = false;
// Convert to unit space
Point? unitHitPoint = Effect.WorldToUnit(originalHitPoint, _bboxSubgraph);
if (unitHitPoint != null)
{
Point transformedPt = new Point();
// Do the transform
if (effectHitTestInverse.TryTransform(unitHitPoint.Value, out transformedPt))
{
// Convert back to world space
Point? worldSpace = Effect.UnitToWorld(transformedPt, _bboxSubgraph);
if (worldSpace != null)
{
hitPoint = worldSpace.Value;
ok = true;
}
}
}
if (!ok)
{
return HitTestResultBehavior.Continue;
}
}
}
else
{
Debug.Assert(BitmapEffectStateField.GetValue(this) != null);
// BitmapEffects are deprecated so they no longer affect hit testing.
}
}
//
// Hit test against the children.
//
if (filter != HitTestFilterBehavior.ContinueSkipChildren)
{
int childCount = VisualChildrenCount;
for (int i=childCount-1; i>=0; i--)
{
Visual child = GetVisualChild(i);
if (child != null)
{
// Hit the scollClip bounds first, which are in the child's outer-space.
Rect? scrollClip = ScrollableAreaClipField.GetValue(child);
if (scrollClip.HasValue && !scrollClip.Value.Contains(hitPoint))
{
// Skip child if the point is not within the ScrollableClip.
continue;
}
//
// Transform the hit-test point below offset and transform.
//
Point newHitPoint = hitPoint;
// Apply the offset.
newHitPoint = newHitPoint - child._offset;
// If we have a transform, apply the transform.
Transform childTransform = TransformField.GetValue(child);
if (childTransform != null)
{
Matrix inv = childTransform.Value;
// If we can't invert the transform, the child is not hitable. This makes sense since
// the node's rendered content is degenerate, i.e. does not really take up any space.
// Skip the child by continuing in the loop.
if (!inv.HasInverse)
{
continue;
}
inv.Invert();
newHitPoint = newHitPoint * inv;
}
// Set the new hittesting point into the hittest params.
pointParams.SetHitPoint(newHitPoint);
// Perform the hit-test against the child.
HitTestResultBehavior result =
child.HitTestPoint(filterCallback, resultCallback, pointParams);
// Restore the hit-test point.
pointParams.SetHitPoint(originalHitPoint);
if (result == HitTestResultBehavior.Stop)
{
return HitTestResultBehavior.Stop;
}
}
}
}
//
// Hit test against the content of this Visual.
//
if (filter != HitTestFilterBehavior.ContinueSkipSelf)
{
// set the transformed hit point
pointParams.SetHitPoint(hitPoint);
HitTestResultBehavior result = HitTestPointInternal(filterCallback, resultCallback, pointParams);
// restore the hit point back to its original
pointParams.SetHitPoint(originalHitPoint);
if (result == HitTestResultBehavior.Stop)
{
return HitTestResultBehavior.Stop;
}
}
}
return HitTestResultBehavior.Continue;
}
// provides a transform that goes between the Visual's coordinate space
// and that after applying the transforms that bring it to outer space.
internal GeneralTransform TransformToOuterSpace()
{
Matrix m = Matrix.Identity;
GeneralTransformGroup group = null;
GeneralTransform result = null;
if (CheckFlagsAnd(VisualFlags.NodeHasEffect))
{
Effect effect = EffectField.GetValue(this);
if (effect != null)
{
GeneralTransform gt = effect.CoerceToUnitSpaceGeneralTransform(
effect.EffectMapping,
VisualDescendantBounds);
Transform affineTransform = gt.AffineTransform;
if (affineTransform != null)
{
Matrix cm = affineTransform.Value;
MatrixUtil.MultiplyMatrix(ref m, ref cm);
}
else
{
group = new GeneralTransformGroup();
group.Children.Add(gt);
}
}
else
{
BitmapEffectState bitmapEffectState = BitmapEffectStateField.GetValue(this);
// If we have an effect on this node and it isn't an Effect, it must be a BitmapEffect.
// Since BitmapEffects are deprecated and ignored, they do not change a Visual's transform.
Debug.Assert(bitmapEffectState != null);
}
}
Transform transform = TransformField.GetValue(this);
if (transform != null)
{
Matrix cm = transform.Value;
MatrixUtil.MultiplyMatrix(ref m, ref cm);
}
m.Translate(_offset.X, _offset.Y); // Consider having a bit that indicates that we have a non-null offset.
if (group == null)
{
result = new MatrixTransform(m);
}
else
{
group.Children.Add(new MatrixTransform(m));
result = group;
}
result.Freeze();
return result;
}
internal HitTestResultBehavior HitTestGeometry(
HitTestFilterCallback filterCallback,
HitTestResultCallback resultCallback,
GeometryHitTestParameters geometryParams)
{
// we do not need parameter checks because they are done in HitTest()
Geometry clip = VisualClip;
if (clip != null)
{
// HitTest with a Geometry and a clip should hit test with
// the intersection of the geometry and the clip, not the entire geometry
IntersectionDetail intersectionDetail = clip.FillContainsWithDetail(geometryParams.InternalHitGeometry);
Debug.Assert(intersectionDetail != IntersectionDetail.NotCalculated);
if (intersectionDetail == IntersectionDetail.Empty)
{
// bail out if there is a clip and this region is not inside
return HitTestResultBehavior.Continue;
}
}
//
// Check if the geometry intersects with our hittest bounds.
// If not, the Visual is not hit-testable at all.
if (_bboxSubgraph.IntersectsWith(geometryParams.Bounds))
{
//
// Determine if there is a special filter behavior defined for this
// Visual.
//
HitTestFilterBehavior filter = HitTestFilterBehavior.Continue;
if (filterCallback != null)
{
filter = filterCallback(this);
if (filter == HitTestFilterBehavior.ContinueSkipSelfAndChildren)
{
return HitTestResultBehavior.Continue;
}
if (filter == HitTestFilterBehavior.Stop)
{
return HitTestResultBehavior.Stop;
}
}
//
// Hit-test against the children.
//
int childCount = VisualChildrenCount;
if (filter != HitTestFilterBehavior.ContinueSkipChildren)
{
for (int i=childCount-1; i>=0; i--)
{
Visual child = GetVisualChild(i);
if (child != null)
{
// Hit the scollClip bounds first, which are in the child's outer-space.
Rect? scrollClip = ScrollableAreaClipField.GetValue(child);
if (scrollClip.HasValue)
{
// Hit-testing with a Geometry and a clip should hit test with
// the intersection of the geometry and the clip, not the entire geometry
RectangleGeometry rectClip = new RectangleGeometry(scrollClip.Value);
IntersectionDetail intersectionDetail = rectClip.FillContainsWithDetail(geometryParams.InternalHitGeometry);
Debug.Assert(intersectionDetail != IntersectionDetail.NotCalculated);
if (intersectionDetail == IntersectionDetail.Empty)
{
// Skip child if there is a scrollable clip and this region is not inside it.
continue;
}
}
// Transform the geometry below offset and transform.
Matrix inv = Matrix.Identity;
inv.Translate(-child._offset.X, -child._offset.Y);
Transform childTransform = TransformField.GetValue(child);
if (childTransform != null)
{
Matrix m = childTransform.Value;
// If we can't invert the transform, the child is not hitable. This makes sense since
// the node's rendered content is degnerated, i.e. does not really take up any space.
// Skipping the child by continuing the loop.
if (!m.HasInverse)
{
continue;
}
// Inverse the transform.
m.Invert();
// Multiply the inverse and the offset together.
// inv = inv * m;
MatrixUtil.MultiplyMatrix(ref inv, ref m);
}
// Push the transform on the geometry params.
geometryParams.PushMatrix(ref inv);
// Hit-Test against the children.
HitTestResultBehavior result =
child.HitTestGeometry(filterCallback, resultCallback, geometryParams);
// Pop the transform from the geometry params.
geometryParams.PopMatrix();
// Process the result.
if (result == HitTestResultBehavior.Stop)
{
return HitTestResultBehavior.Stop;
}
}
}
}
//
// Hit-test against the content of the Visual.
//
if (filter != HitTestFilterBehavior.ContinueSkipSelf)
{
GeometryHitTestResult hitResult = HitTestCore(geometryParams);
if (hitResult != null)
{
Debug.Assert(resultCallback != null);
return resultCallback(hitResult);
}
}
}
return HitTestResultBehavior.Continue;
}
/// <summary>
/// This method provides an internal extension point for Viewport3DVisual
/// to grab the HitTestFilterCallback and ResultDelegate before it gets lost in the
/// forward to HitTestCore.
/// </summary>
internal virtual HitTestResultBehavior HitTestPointInternal(
HitTestFilterCallback filterCallback,
HitTestResultCallback resultCallback,
PointHitTestParameters hitTestParameters)
{
HitTestResult hitResult = HitTestCore(hitTestParameters);
if (hitResult != null)
{
return resultCallback(hitResult);
}
return HitTestResultBehavior.Continue;
}
/// <summary>
/// HitTestCore implements whether we have hit the bounds of this visual.
/// </summary>
protected virtual HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
ArgumentNullException.ThrowIfNull(hitTestParameters);
// If we don't have a clip, or if the clip contains the point, keep going.
if (GetHitTestBounds().Contains(hitTestParameters.HitPoint))
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
else
{
return null;
}
}
/// <summary>
/// HitTestCore implements whether we have hit the bounds of this visual.
/// </summary>
protected virtual GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
{
ArgumentNullException.ThrowIfNull(hitTestParameters);
IntersectionDetail intersectionDetail;
RectangleGeometry contentGeometry = new RectangleGeometry(GetHitTestBounds());
intersectionDetail = contentGeometry.FillContainsWithDetail(hitTestParameters.InternalHitGeometry);
Debug.Assert(intersectionDetail != IntersectionDetail.NotCalculated);
if (intersectionDetail != IntersectionDetail.Empty)
{
return new GeometryHitTestResult(this, intersectionDetail);
}
return null;
}
#endregion Hit Testing
// --------------------------------------------------------------------
//
// Visual Operations API
//
// --------------------------------------------------------------------
#region VisualChildren
/// <summary>
/// Derived classes override this property to enable the Visual code to enumerate
/// the Visual children. Derived classes need to return the number of children
/// from this method.
///
/// By default a Visual does not have any children.
///
/// Remark: During this virtual method the Visual tree must not be modified.
/// </summary>
protected virtual int VisualChildrenCount
{
get { return 0; }
}
/// <summary>
/// Returns the number of 2D children. This returns 0 for visuals
/// whose children are Visual3Ds.
/// </summary>
internal int InternalVisualChildrenCount
{
get
{
// Call the right virtual method.
return VisualChildrenCount;
}
}
/// <summary>
/// Returns the number of children of this object (in most cases this will be
/// the number of Visuals, but it some cases, Viewport3DVisual for instance,
/// this is the number of Visual3Ds).
///
/// Used only by VisualTreeHelper.
/// </summary>
internal virtual int InternalVisual2DOr3DChildrenCount
{
get
{
// Call the right virtual method.
return VisualChildrenCount;
}
}
///<Summary>
///Flag to check if this visual has any children
///</Summary>
internal bool HasVisualChildren
{
get
{
return ((_flags & VisualFlags.HasChildren) != 0);
}
}
/// <summary>
/// Derived class must implement to support Visual children. The method must return
/// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
///
/// By default a Visual does not have any children.
///
/// Remark:
/// Need to lock down Visual tree during the callbacks.
/// During this virtual call it is not valid to modify the Visual tree.
///
/// It is okay to type this protected API to the 2D Visual. The only 2D Visual with
/// 3D childern is the Viewport3DVisual which is sealed
/// </summary>
protected virtual Visual GetVisualChild(int index)
{
throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
}
/// <summary>
/// Returns the 2D child at index "index". This will fail for Visuals
/// whose children are Visual3Ds.
/// </summary>
internal Visual InternalGetVisualChild(int index)
{
// Call the right virtual method.
return GetVisualChild(index);
}
/// <summary>
/// Returns the child at index "index" (in most cases this will be
/// a Visual, but it some cases, Viewport3DVisual for instance,
/// this is a Visual3D).
///
/// Used only by VisualTreeHelper.
/// </summary>
internal virtual DependencyObject InternalGet2DOr3DVisualChild(int index)
{
// Call the right virtual method.
return GetVisualChild(index);
}
/// <summary>
/// Helper method to provide access to AddVisualChild for the VisualCollection.
/// </summary>
internal void InternalAddVisualChild(Visual child)
{
this.AddVisualChild(child);
}
/// <summary>
/// Helper method to provide access to RemoveVisualChild for the VisualCollection.
/// </summary>
internal void InternalRemoveVisualChild(Visual child)
{
this.RemoveVisualChild(child);
}
/// <summary>
/// AttachChild
///
/// Derived classes must call this method to notify the Visual layer that a new
/// child appeard in the children collection. The Visual layer will then call the GetVisualChild
/// method to find out where the child was added.
///
/// Remark: To move a Visual child in a collection it must be first disconnected and then connected
/// again. (Moving forward we might want to add a special optimization there so that we do not
/// unmarshal our composition resources).
///
/// It is okay to type this protected API to the 2D Visual. The only 2D Visual with
/// 3D childern is the Viewport3DVisual which is sealed.
/// </summary>
protected void AddVisualChild(Visual child)
{
if (child == null)
{
return;
}
if (child._parent != null)
{
throw new ArgumentException(SR.Visual_HasParent);
}
// invalid during a VisualTreeChanged event
VisualDiagnostics.VerifyVisualTreeChange(this);
SetFlags(true, VisualFlags.HasChildren);
// Set the parent pointer.
child._parent = this;
//
// The child might be dirty. Hence we need to propagate dirty information
// from the parent and from the child.
//
Visual.PropagateFlags(
this,
VisualFlags.IsSubtreeDirtyForPrecompute,
VisualProxyFlags.IsSubtreeDirtyForRender);
Visual.PropagateFlags(
child,
VisualFlags.IsSubtreeDirtyForPrecompute,
VisualProxyFlags.IsSubtreeDirtyForRender);
//
// Resume layout.
//
UIElement.PropagateResumeLayout(this, child);
if (HwndTarget.IsProcessPerMonitorDpiAware == true && HwndTarget.IsPerMonitorDpiScalingEnabled)
{
bool flag1 = CheckFlagsAnd(VisualFlags.DpiScaleFlag1);
bool flag2 = CheckFlagsAnd(VisualFlags.DpiScaleFlag2);
int index = 0; // dummy value;
if (flag1 && flag2)
{
index = DpiIndex.GetValue(this);
}
child.RecursiveSetDpiScaleVisualFlags(new DpiRecursiveChangeArgs(new DpiFlags(flag1, flag2, index),
child.GetDpi(), this.GetDpi()));
}
// Fire notifications
this.OnVisualChildrenChanged(child, null /* no removed child */);
child.FireOnVisualParentChanged(null);
VisualDiagnostics.OnVisualChildChanged(this, child, true);
}
/// <summary>
/// DisconnectChild
///
/// Derived classes must call this method to notify the Visual layer that a
/// child was removed from the children collection. The Visual layer will then call
/// GetChildren to find out which child has been removed.
///
/// </summary>
protected void RemoveVisualChild(Visual child)
{
if (child == null || child._parent == null)
{
return;
}
if (child._parent != this)
{
throw new ArgumentException(SR.Visual_NotChild);
}
// invalid during a VisualTreeChanged event
VisualDiagnostics.VerifyVisualTreeChange(this);
VisualDiagnostics.OnVisualChildChanged(this, child, false);
if (InternalVisual2DOr3DChildrenCount == 0)
{
SetFlags(false, VisualFlags.HasChildren);
}
//
// Remove the child on all channels its current parent is marshalled to.
//
for (int i = 0; i < _proxy.Count; i++)
{
DUCE.Channel channel = _proxy.GetChannel(i);
if (child.CheckFlagsAnd(channel, VisualProxyFlags.IsConnectedToParent))
{
child.SetFlags(channel, false, VisualProxyFlags.IsConnectedToParent);
DUCE.IResource childResource = (DUCE.IResource)child;
childResource.RemoveChildFromParent(this, channel);
childResource.ReleaseOnChannel(channel);
}
}
// Set the parent pointer to null.
child._parent = null;
Visual.PropagateFlags(
this,
VisualFlags.IsSubtreeDirtyForPrecompute,
VisualProxyFlags.IsSubtreeDirtyForRender);
UIElement.PropagateSuspendLayout(child);
// Fire notifications
child.FireOnVisualParentChanged(this);
OnVisualChildrenChanged(null /* no child added */, child);
}
/// <summary>
/// InvalidateZOrder
/// Note: must do invalidation without removing / adding
/// to avoid loosing focused element by input system
/// </summary>
internal void InvalidateZOrder()
{
// if we don't have any children, there is nothing to do
if (VisualChildrenCount == 0)
return;
Visual.PropagateFlags(
this,
VisualFlags.IsSubtreeDirtyForPrecompute,
VisualProxyFlags.IsSubtreeDirtyForRender);
this.SetFlagsOnAllChannels(true, VisualProxyFlags.IsChildrenZOrderDirty);
// This is a workaround
// Input system needs to be notified about the changes on screen to be able to re-hittest
System.Windows.Input.InputManager.SafeCurrentNotifyHitTestInvalidated();
}
//This is used by LayoutManager as a perf optimization for layout updates.
//During layout updates, LM needs to find which areas of the visual tree
//are higher in the tree - they have to be processed first to avoid multiple
//updates of lower descendants. The tree level counter is maintained by
//UIElement.PropagateResume/SuspendLayout methods and uses 8 bits in VisualFlags to
//keep the count.
internal uint TreeLevel
{
get
{
return ((uint)_flags & 0xFFE00000) >> 21;
}
set
{
if(value > TreeLevelLimit)
{
throw new InvalidOperationException(SR.Format(SR.LayoutManager_DeepRecursion, TreeLevelLimit));
}
_flags = (VisualFlags)(((uint)_flags & 0x001FFFFF) | (value << 21));
}
}
#endregion VisualChildren
#region VisualParent
/// <summary>
/// Returns the parent of this Visual. Parent may be either a Visual or Visual3D.
/// </summary>
protected DependencyObject VisualParent
{
get
{
VerifyAPIReadOnly();
return InternalVisualParent;
}
}
/// <summary>
/// Identical to VisualParent, except that skips verify access for perf.
/// </summary>
internal DependencyObject InternalVisualParent
{
get
{
return _parent;
}
}
#endregion VisualParent
// These 2 method will be REMOVED once Hamid is back and can
// explain why Window needs to Bypass layout for setting Flow Direction.
// These methods are only called from InternalSetLayoutTransform which is called only from Window
internal void InternalSetOffsetWorkaround(Vector offset)
{
VisualOffset = offset;
}
internal void InternalSetTransformWorkaround(Transform transform)
{
VisualTransform = transform;
}
// --------------------------------------------------------------------
//
// Visual Properties
//
// --------------------------------------------------------------------
#region Visual Properties
/// <summary>
/// Gets or sets the transform of this Visual.
/// </summary>
protected internal Transform VisualTransform
{
get
{
VerifyAPIReadOnly();
return TransformField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite(value);
Transform transform = TransformField.GetValue(this);
if (transform == value)
{
return;
}
Transform newTransform = value;
// Add changed notifications for the new transform if necessary.
if (newTransform != null && !newTransform.IsFrozen)
{
newTransform.Changed += TransformChangedHandler;
}
if (transform != null)
{
//
// Remove changed notifications for the old transform if necessary.
//
if (!transform.IsFrozen)
{
transform.Changed -= TransformChangedHandler;
}
//
// Disconnect the transform from this visual.
//
DisconnectAttachedResource(
VisualProxyFlags.IsTransformDirty,
((DUCE.IResource)transform));
}
//
// Set the new clip and mark it dirty
//
TransformField.SetValue(this, newTransform);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsTransformDirty);
TransformChanged(/* sender */ null, /* args */ null);
}
}
/// <summary>
/// Gets or sets the Effect of this Visual.
/// </summary>
protected internal Effect VisualEffect
{
get
{
VerifyAPIReadOnly();
return VisualEffectInternal;
}
protected set
{
VerifyAPIReadWrite(value);
// Legacy BitmapEffects and new Effects cannot be mixed because the new image effect
// pipeline may be used to emulate a legacy BitmapEffect.
BitmapEffectState bed = UserProvidedBitmapEffectData.GetValue(this);
if (bed != null)
{
if (value != null) // UIElement has a tendency to set a lot of properties to null even if it
// never set a property to a different value in the first place.
{
// If a BitmapEffect is set, the user cannot set an Effect, since
// mixing of legacy BitmapEffects is not allowed with Effects.
throw new Exception(SR.Effect_CombinedLegacyAndNew);
}
else
{
return;
}
}
VisualEffectInternal = value;
}
}
/// <summary>
/// Internal accessor to image effect property that gets or sets the Effect of this Visual.
/// The internal accessor is used by the VisualBitmapEffect emulation layer to avoid some of the
/// compatibility checks in the protected VisualEffect property.
/// </summary>
internal Effect VisualEffectInternal
{
get
{
// Legacy BitmapEffects and new Effects cannot be mixed because the new image effect
// pipeline may be used to emulate a legacy BitmapEffect. Therefore, if a BitmapEffect is
// assigned to this node, the Effect is conceptually not set and null must be returned
// from this getter. If no BitmapEffect is set on this node, the Effect has been provided
// by the user and therefore the Effect is returned.
if (NodeHasLegacyBitmapEffect)
{
return null;
}
else
{
return EffectField.GetValue(this);
}
}
set
{
Effect imageEffect = EffectField.GetValue(this);
if (imageEffect == value)
{
return;
}
Effect newEffect = value;
// Add changed notifications for the new Effect if necessary.
if (newEffect != null && !newEffect.IsFrozen)
{
newEffect.Changed += EffectChangedHandler;
}
if (imageEffect != null)
{
//
// Remove changed notifications for the old Effect if necessary.
//
if (!imageEffect.IsFrozen)
{
imageEffect.Changed -= EffectChangedHandler;
}
//
// Disconnect the Effect from this visual.
//
DisconnectAttachedResource(
VisualProxyFlags.IsEffectDirty,
((DUCE.IResource)imageEffect));
}
//
// Set the new effect and mark it dirty
//
SetFlags(newEffect != null, VisualFlags.NodeHasEffect);
EffectField.SetValue(this, newEffect);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsEffectDirty);
EffectChanged(/* sender */ null, /* args */ null);
}
}
/// <summary>
/// BitmapEffect Property -
/// Gets or sets the optional BitmapEffect. If set, the BitmapEffect will
/// be applied Visual's rendered content, after which the OpacityMask and/or Opacity
/// will be applied (if present).
/// </summary>
[Obsolete(MS.Internal.Media.VisualTreeUtils.BitmapEffectObsoleteMessage)]
protected internal BitmapEffect VisualBitmapEffect
{
get
{
VerifyAPIReadOnly();
BitmapEffectState bed = UserProvidedBitmapEffectData.GetValue(this);
if (bed != null)
{
return bed.BitmapEffect;
}
else
{
return null;
}
}
protected set
{
VerifyAPIReadWrite(value);
//
// Figure out if a image effect has been provided by the user. If so, calling this API is illegal
// since new Effects and legacy BitmapEffects cannot be mixed.
Effect imageEffect = EffectField.GetValue(this);
BitmapEffectState bed = UserProvidedBitmapEffectData.GetValue(this);
if ( (bed == null)
&& (imageEffect != null))
{
if (value != null) // Allowing incoming value of null because UIElements tend
// to aggressively set this property to null even if it has never been set.
{
// If no BitmapEffect is set and an Effect is set, the Effect has been
// provided by the user and not by emulation. Since mixing of legacy
// BitmapEffects is not allowed with Effects, setting a BitmapEffect is illegal.
throw new Exception(SR.Effect_CombinedLegacyAndNew);
}
else
{
return;
}
}
//
// To enable emulation of the legacy effects on top of the new effects pipeline, store the
// bitmap effect information in our staging uncommon field: UserProvidedBitmapEffectData.
BitmapEffect oldBitmapEffect = (bed == null) ? null : bed.BitmapEffect;
if (oldBitmapEffect == value) // If new and old value are the same, this set call can be treated as a no-op.
{
return;
}
BitmapEffect newBitmapEffect = value;
if (newBitmapEffect == null)
{
Debug.Assert(bed != null, "Must be non-null because otherwise the code would have earlied out where new value is compared against old value.");
// The following line of code will effectively set the BitmapEffectInput property to null. This is strange behavior for WPF properties, but follows the
// original BitmapEffects implementation.
UserProvidedBitmapEffectData.SetValue(this, null);
}
else
{
if (bed == null)
{
bed = new BitmapEffectState();
UserProvidedBitmapEffectData.SetValue(this, bed);
}
bed.BitmapEffect = newBitmapEffect;
}
if (newBitmapEffect != null && !newBitmapEffect.IsFrozen)
{
newBitmapEffect.Changed += new EventHandler(BitmapEffectEmulationChanged);
}
if (oldBitmapEffect != null && !oldBitmapEffect.IsFrozen)
{
oldBitmapEffect.Changed -= new EventHandler(BitmapEffectEmulationChanged);
}
// Notify about the bitmap effect changes to configure the new emulation.
BitmapEffectEmulationChanged(/* sender */ null, /* args */ null);
}
}
/// <summary>
/// BitmapEffectInput Property -
/// Gets or sets the optional BitmapEffectInput. If set, the BitmapEffectInput will
/// be applied Visual's rendered content, after which the OpacityMask and/or Opacity
/// will be applied (if present).
/// </summary>
[Obsolete(MS.Internal.Media.VisualTreeUtils.BitmapEffectObsoleteMessage)]
protected internal BitmapEffectInput VisualBitmapEffectInput
{
get
{
VerifyAPIReadOnly();
BitmapEffectState bed = UserProvidedBitmapEffectData.GetValue(this);
if (bed != null)
{
return bed.BitmapEffectInput;
}
else
{
return null;
}
}
protected set
{
VerifyAPIReadWrite(value);
//
// Figure out if a image effect has been provided by the user. If so, calling this API is illegal
// sinc new Effects and legacy BitmapEffects cannot be mixed.
Effect imageEffect = EffectField.GetValue(this);
BitmapEffectState bed = UserProvidedBitmapEffectData.GetValue(this);
if ((bed == null) && (imageEffect != null))
{
if (value != null) // Allowing null because parser and UIElement tend to set this property to null
// even if it has never been set to non-null.
{
// If no BitmapEffect is set and an Effect is set, the Effect has been
// provided by the user. Since mixing of legacy BitmapEffects is not allowed with
// Effects, setting a BitmapEffect is illegal.
throw new Exception(SR.Effect_CombinedLegacyAndNew);
}
else
{
return;
}
}
//
// To enable emulation of the legacy effects on top of the new effects pipeline, store the
// bitmap effect input information in our staging uncommon field: UserProvidedBitmapEffectData.
BitmapEffectInput oldBitmapEffectInput = (bed == null) ? null : bed.BitmapEffectInput;
BitmapEffectInput newBitmapEffectInput = value;
if (oldBitmapEffectInput == newBitmapEffectInput) // If new and old value are the same, this set call can be treated as a no-op.
{
return;
}
// Make sure there is a BitmapEffectData instance allocated.
if (bed == null)
{
bed = new BitmapEffectState();
UserProvidedBitmapEffectData.SetValue(this, bed);
}
bed.BitmapEffectInput = newBitmapEffectInput;
if (newBitmapEffectInput != null && !newBitmapEffectInput.IsFrozen)
{
newBitmapEffectInput.Changed += new EventHandler(BitmapEffectEmulationChanged);
}
if (oldBitmapEffectInput != null && !oldBitmapEffectInput.IsFrozen)
{
oldBitmapEffectInput.Changed -= new EventHandler(BitmapEffectEmulationChanged);
}
// Notify about the bitmap effect changes to configure the new emulation.
BitmapEffectEmulationChanged(/* sender */ null, /* args */ null);
}
}
// <summary>
// This handler reconfigures the bitmap effects pipeline whenever anything changes. It is
// responsible for figuring out if a legacy effect can be emulated on the new pipeline or
// not.
// </summary>
internal void BitmapEffectEmulationChanged(object sender, EventArgs e)
{
BitmapEffectState bed = UserProvidedBitmapEffectData.GetValue(this);
BitmapEffect currentBitmapEffect = (bed == null) ? null : bed.BitmapEffect;
BitmapEffectInput currentBitmapEffectInput = (bed == null) ? null : bed.BitmapEffectInput;
// Note that when this method is called, a legacy BitmapEffect has been set or reset on
// the Visual by the user. The next step is to try to emulate the effect in case the current
// effect is non null or reset the emulation layer if the user has set the effect to null.
if (currentBitmapEffect == null)
{
// This means the effect has been disconnected from this Visual. Setting the internal
// bitmap effect property and the image effect property to null to disconnect all the
// effects. The Effect property needs to be set to null because the effect might
// be emulated.
VisualBitmapEffectInternal = null;
VisualBitmapEffectInputInternal = null;
VisualEffectInternal = null;
}
else if (currentBitmapEffectInput != null)
{
// If a BitmapEffectInput is specified, make sure the legacy effect is not being
// emulated using the Effect pipeline since the new pipeline does not support
// BitmapEffecInputs.
VisualEffectInternal = null;
VisualBitmapEffectInternal = currentBitmapEffect;
VisualBitmapEffectInputInternal = currentBitmapEffectInput;
}
else if (RenderCapability.IsShaderEffectSoftwareRenderingSupported &&
currentBitmapEffect.CanBeEmulatedUsingEffectPipeline() &&
(!CheckFlagsAnd(VisualFlags.BitmapEffectEmulationDisabled)))
{
// If we can emulate the effect switch to emulating it.
VisualBitmapEffectInternal = null;
VisualBitmapEffectInputInternal = null;
Effect emulatingEffect = currentBitmapEffect.GetEmulatingEffect();
Debug.Assert(currentBitmapEffect.IsFrozen == emulatingEffect.IsFrozen);
VisualEffectInternal = emulatingEffect;
}
else
{
// Cannot emulate the effect, using legacy pipeline.
VisualEffectInternal = null;
VisualBitmapEffectInputInternal = null;
VisualBitmapEffectInternal = currentBitmapEffect;
}
}
/// <summary>
/// Used by the test team to disable bitmap effect emulation for testing purposes.
/// </summary>
internal bool BitmapEffectEmulationDisabled
{
get
{
return CheckFlagsAnd(VisualFlags.BitmapEffectEmulationDisabled);
}
set
{
if (value != CheckFlagsAnd(VisualFlags.BitmapEffectEmulationDisabled))
{
SetFlags(value, VisualFlags.BitmapEffectEmulationDisabled);
// Notify about the bitmap effect changes to configure the new emulation.
BitmapEffectEmulationChanged(/* sender */ null, /* args */ null);
}
}
}
/// <summary>
/// Internal accessor to BitmapEffect property that gets or sets the BitmapEffect of this Visual.
/// The internal accessor is used by the VisualBitmapEffect emulation layer to avoid some of the
/// compatibility checks in the protected VisualBitmapEffect property.
/// </summary>
internal BitmapEffect VisualBitmapEffectInternal
{
get
{
VerifyAPIReadOnly();
if (NodeHasLegacyBitmapEffect)
{
return BitmapEffectStateField.GetValue(this).BitmapEffect;
}
else
{
return null;
}
}
set
{
BitmapEffectState bitmapEffectState = BitmapEffectStateField.GetValue(this);
BitmapEffect bitmapEffect = (bitmapEffectState == null) ? null : bitmapEffectState.BitmapEffect;
if (bitmapEffect == value)
{
return;
}
BitmapEffect newBitmapEffect = value;
if (newBitmapEffect == null)
{
Debug.Assert(bitmapEffectState != null);
BitmapEffectStateField.SetValue(this, null);
}
else
{
if (bitmapEffectState == null)
{
bitmapEffectState = new BitmapEffectState();
BitmapEffectStateField.SetValue(this, bitmapEffectState);
}
bitmapEffectState.BitmapEffect = newBitmapEffect;
Debug.Assert(EffectField.GetValue(this) == null, "Not expecting both BitmapEffect and Effect to be set on the same node");
}
}
}
/// <summary>
/// Internal accessor to BitmapEffectInput property that gets or sets the BitmapEffectInput of this Visual.
/// The internal accessor is used by the VisualBitmapEffect emulation layer to avoid some of the
/// compatibility checks in the protected VisualBitmapEffectInput property.
/// </summary>
internal BitmapEffectInput VisualBitmapEffectInputInternal
{
get
{
VerifyAPIReadOnly();
BitmapEffectState bitmapEffectState = BitmapEffectStateField.GetValue(this);
if (bitmapEffectState != null)
return bitmapEffectState.BitmapEffectInput;
return null;
}
set
{
VerifyAPIReadWrite();
BitmapEffectState bitmapEffectState = BitmapEffectStateField.GetValue(this);
BitmapEffectInput bitmapEffectInput = (bitmapEffectState == null) ? null : bitmapEffectState.BitmapEffectInput;
if (bitmapEffectInput == value)
{
return;
}
BitmapEffectInput newBitmapEffectInput = value;
if (bitmapEffectState == null)
{
bitmapEffectState = new BitmapEffectState();
BitmapEffectStateField.SetValue(this, bitmapEffectState);
}
bitmapEffectState.BitmapEffectInput = newBitmapEffectInput;
}
}
/// <summary>
/// Gets or sets the caching behavior for the Visual.
/// </summary>
protected internal CacheMode VisualCacheMode
{
get
{
VerifyAPIReadOnly();
return CacheModeField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite(value);
CacheMode cacheMode = CacheModeField.GetValue(this);
if (cacheMode == value)
{
return;
}
CacheMode newCacheMode = value;
// Add changed notifications for the new cache mode if necessary.
if (newCacheMode != null && !newCacheMode.IsFrozen)
{
newCacheMode.Changed += CacheModeChangedHandler;
}
if (cacheMode != null)
{
//
// Remove changed notifications for the old cache mode if necessary.
//
if (!cacheMode.IsFrozen)
{
cacheMode.Changed -= CacheModeChangedHandler;
}
//
// Disconnect the cache mode from this visual.
//
DisconnectAttachedResource(
VisualProxyFlags.IsCacheModeDirty,
((DUCE.IResource)cacheMode));
}
//
// Set the new cache mode and mark it dirty
//
CacheModeField.SetValue(this, newCacheMode);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsCacheModeDirty);
CacheModeChanged(/* sender */ null, /* args */ null);
}
}
/// <summary>
/// Gets or sets the scrollable area clip for the Visual.
/// </summary>
protected internal Rect? VisualScrollableAreaClip
{
get
{
VerifyAPIReadOnly();
return ScrollableAreaClipField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite();
Rect? currentValue = ScrollableAreaClipField.GetValue(this);
if (currentValue != value)
{
ScrollableAreaClipField.SetValue(this, value);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsScrollableAreaClipDirty);
ScrollableAreaClipChanged(/* sender */ null, /* args */ null);
}
}
}
/// <summary>
/// Gets or sets the clip of this Visual.
/// </summary>
protected internal Geometry VisualClip
{
get
{
VerifyAPIReadOnly();
return ClipField.GetValue(this);
}
protected set
{
ChangeVisualClip(value, false /* dontSetWhenClose */);
}
}
/// <summary>
/// Processes changing the clip from the old clip to the new clip.
/// Called from Visual.set_VisualClip and from places that want
/// to optimize setting a new clip (like UIElement.ensureClip).
/// </summary>
internal void ChangeVisualClip(Geometry newClip, bool dontSetWhenClose)
{
VerifyAPIReadWrite(newClip);
Geometry oldClip = ClipField.GetValue(this);
if ((oldClip == newClip) ||
(dontSetWhenClose && (oldClip != null) && (newClip != null) && oldClip.AreClose(newClip)))
{
return;
}
// Add changed notifications for the new clip if necessary.
if (newClip != null && !newClip.IsFrozen)
{
newClip.Changed += ClipChangedHandler;
}
if (oldClip != null)
{
//
// Remove changed notifications for the old clip if necessary.
//
if (!oldClip.IsFrozen)
{
oldClip.Changed -= ClipChangedHandler;
}
//
// Disconnect the clip from this visual.
//
DisconnectAttachedResource(
VisualProxyFlags.IsClipDirty,
((DUCE.IResource)oldClip));
}
//
// Set the new clip and mark it dirty
//
ClipField.SetValue(this, newClip);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsClipDirty);
ClipChanged(/* sender */ null, /* args */ null);
}
/// <summary>
/// Gets and sets the offset.
/// </summary>
protected internal Vector VisualOffset
{
get
{
// VerifyAPIReadOnly(); // Intentionally removed for performance reasons.
return _offset;
}
protected set
{
VerifyAPIReadWrite();
if (value != _offset) // Fuzzy comparison might be better here.
{
VisualFlags flags;
_offset = value;
SetFlagsOnAllChannels(true, VisualProxyFlags.IsOffsetDirty);
flags = VisualFlags.IsSubtreeDirtyForPrecompute;
PropagateFlags(
this,
flags,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
}
}
/// <summary>
/// Gets or sets the opacity of the Visual.
/// </summary>
protected internal double VisualOpacity
{
get
{
VerifyAPIReadOnly();
return OpacityField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite();
if (OpacityField.GetValue(this) == value)
{
return;
}
OpacityField.SetValue(this, value);
// Microsoft: We need to do more here for animated opacity.
SetFlagsOnAllChannels(true, VisualProxyFlags.IsOpacityDirty);
PropagateFlags(
this,
VisualFlags.None,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
}
/// <summary>
/// Gets or sets the EdgeMode of the Visual.
/// </summary>
protected internal EdgeMode VisualEdgeMode
{
get
{
VerifyAPIReadOnly();
return EdgeModeField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite();
if (EdgeModeField.GetValue(this) == value)
{
return;
}
EdgeModeField.SetValue(this, value);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsEdgeModeDirty);
PropagateFlags(
this,
VisualFlags.None,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
}
/// <summary>
/// Gets or sets the ImageScalingMode of the Visual.
/// </summary>
protected internal BitmapScalingMode VisualBitmapScalingMode
{
get
{
VerifyAPIReadOnly();
return BitmapScalingModeField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite();
if (BitmapScalingModeField.GetValue(this) == value)
{
return;
}
BitmapScalingModeField.SetValue(this, value);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsBitmapScalingModeDirty);
PropagateFlags(
this,
VisualFlags.None,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
}
/// <summary>
/// Gets or sets the ClearTypeHint of the Visual.
/// </summary>
protected internal ClearTypeHint VisualClearTypeHint
{
get
{
VerifyAPIReadOnly();
return ClearTypeHintField.GetValue(this);
}
set
{
VerifyAPIReadWrite();
if (ClearTypeHintField.GetValue(this) == value)
{
return;
}
ClearTypeHintField.SetValue(this, value);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsClearTypeHintDirty);
PropagateFlags(
this,
VisualFlags.None,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
}
/// <summary>
/// Gets or sets the TextRenderingMode of the Visual.
/// </summary>
protected internal TextRenderingMode VisualTextRenderingMode
{
get
{
VerifyAPIReadOnly();
return TextRenderingModeField.GetValue(this);
}
set
{
VerifyAPIReadWrite();
if (TextRenderingModeField.GetValue(this) == value)
{
return;
}
TextRenderingModeField.SetValue(this, value);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsTextRenderingModeDirty);
PropagateFlags(
this,
VisualFlags.None,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
}
/// <summary>
/// Gets or sets the TextRenderingMode of the Visual.
/// </summary>
protected internal TextHintingMode VisualTextHintingMode
{
get
{
VerifyAPIReadOnly();
return TextHintingModeField.GetValue(this);
}
set
{
VerifyAPIReadWrite();
if (TextHintingModeField.GetValue(this) == value)
{
return;
}
TextHintingModeField.SetValue(this, value);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsTextHintingModeDirty);
PropagateFlags(
this,
VisualFlags.None,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
}
/// <summary>
/// OpacityMask Property -
/// Gets or sets the optional OpacityMask. If set, the Brush's opacity will
/// be combined multiplicitively with the Visual's rendered content.
/// </summary>
protected internal Brush VisualOpacityMask
{
get
{
VerifyAPIReadOnly();
return OpacityMaskField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite(value);
Brush opacityMask = OpacityMaskField.GetValue(this);
if (opacityMask == value)
{
return;
}
Brush newOpacityMask = value;
// Add changed notifications for the new opacity mask if necessary.
if (newOpacityMask != null && !newOpacityMask.IsFrozen)
{
newOpacityMask.Changed += OpacityMaskChangedHandler;
}
if (opacityMask != null)
{
//
// Remove changed notifications for the old opacity mask if necessary.
//
if (!opacityMask.IsFrozen)
{
opacityMask.Changed -= OpacityMaskChangedHandler;
}
//
// Disconnect the opacity mask from this visual.
//
DisconnectAttachedResource(
VisualProxyFlags.IsOpacityMaskDirty,
((DUCE.IResource)opacityMask));
}
//
// Set the new opacity mask and mark it dirty
//
OpacityMaskField.SetValue(this, newOpacityMask);
SetFlagsOnAllChannels(true, VisualProxyFlags.IsOpacityMaskDirty);
OpacityMaskChanged(/* sender */ null, /* args */ null);
}
}
/// <summary>
/// Gets or sets X- (vertical) guidelines on this Visual.
/// </summary>
protected internal DoubleCollection VisualXSnappingGuidelines
{
get
{
VerifyAPIReadOnly();
return GuidelinesXField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite(value);
DoubleCollection guidelines = GuidelinesXField.GetValue(this);
if (guidelines == value)
{
return;
}
DoubleCollection newGuidelines = value;
// Add changed notifications for the new guidelines if necessary.
if (newGuidelines != null && !newGuidelines.IsFrozen)
{
newGuidelines.Changed += GuidelinesChangedHandler;
}
// Remove changed notifications for the old guidelines if necessary.
if (guidelines != null && !guidelines.IsFrozen)
{
guidelines.Changed -= GuidelinesChangedHandler;
}
GuidelinesXField.SetValue(this, newGuidelines);
GuidelinesChanged(/* sender */ null, /* args */ null);
}
}
/// <summary>
/// Gets or sets Y- (horizontal) guidelines of this Visual.
/// </summary>
protected internal DoubleCollection VisualYSnappingGuidelines
{
get
{
VerifyAPIReadOnly();
return GuidelinesYField.GetValue(this);
}
protected set
{
VerifyAPIReadWrite(value);
DoubleCollection guidelines = GuidelinesYField.GetValue(this);
if (guidelines == value)
{
return;
}
DoubleCollection newGuidelines = value;
// Add changed notifications for the new guidelines if necessary.
if (newGuidelines != null && !newGuidelines.IsFrozen)
{
newGuidelines.Changed += GuidelinesChangedHandler;
}
// Remove changed notifications for the old guidelines if necessary.
if (guidelines != null && !guidelines.IsFrozen)
{
guidelines.Changed -= GuidelinesChangedHandler;
}
GuidelinesYField.SetValue(this, newGuidelines);
GuidelinesChanged(/* sender */ null, /* args */ null);
}
}
#endregion Visual Properties
/// <summary>
/// Disconnects a resource attached to this visual.
/// </summary>
internal void DisconnectAttachedResource(
VisualProxyFlags correspondingFlag,
DUCE.IResource attachedResource)
{
//
// We need a special case for the content (corresponding
// to the IsContentConnected flag).
//
bool needToReleaseContent =
correspondingFlag == VisualProxyFlags.IsContentConnected;
//
// Iterate over the channels this visual is being marshaled to
//
for (int i = 0; i < _proxy.Count; i++)
{
DUCE.Channel channel = _proxy.GetChannel(i);
VisualProxyFlags flags = _proxy.GetFlags(i);
//
// See if the corresponding flag is set...
//
bool correspondingFlagSet =
(flags & correspondingFlag) != 0;
//
// We want to perform an action if IsContentConnected
// flag is set or a Is*Dirty flag is not set:
//
if (correspondingFlagSet == needToReleaseContent)
{
//
// Set the flag so that during render we send
// update to the compositor.
//
SetFlags(channel, true, correspondingFlag);
attachedResource.ReleaseOnChannel(channel);
if (needToReleaseContent)
{
//
// Mark the content of this visual as disconnected.
//
_proxy.SetFlags(i, false, VisualProxyFlags.IsContentConnected);
}
}
}
}
/// <summary>
/// GetDrawing - Returns the Drawing content of this Visual
/// </summary>
internal virtual DrawingGroup GetDrawing()
{
VerifyAPIReadOnly();
// Default implementation returns null for Visual's that
// don't have drawings
return null;
}
// --------------------------------------------------------------------
//
// Visual Ancestry Relations
//
// --------------------------------------------------------------------
#region Visual Ancestry Relations
/// <summary>
/// This is called when the parent link of the Visual is changed.
/// This method executes important base functionality before calling the
/// overridable virtual.
/// </summary>
/// <param name="oldParent">Old parent or null if the Visual did not have a parent before.</param>
internal virtual void FireOnVisualParentChanged(DependencyObject oldParent)
{
// Call the ParentChanged virtual before firing the Ancestor Changed Event
OnVisualParentChanged(oldParent);
// Clean up bits when the tree is Cut or Pasted.
// If we are attaching to a tree then
// send the bit up if we need to.
if(oldParent == null)
{
Debug.Assert(_parent != null, "If oldParent is null, current parent should != null.");
if(CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
{
SetTreeBits(
_parent,
VisualFlags.SubTreeHoldsAncestorChanged,
VisualFlags.RegisteredForAncestorChanged);
}
}
// If we are cutting a sub tree off then
// clear the bit in the main tree above if we need to.
else
{
if(CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
{
ClearTreeBits(
oldParent,
VisualFlags.SubTreeHoldsAncestorChanged,
VisualFlags.RegisteredForAncestorChanged);
}
}
// Fire the Ancestor changed Event on the nodes.
AncestorChangedEventArgs args = new AncestorChangedEventArgs(this, oldParent);
ProcessAncestorChangedNotificationRecursive(this, args);
}
/// <summary>
/// OnVisualParentChanged is called when the parent of the Visual is changed.
/// </summary>
/// <param name="oldParent">Old parent or null if the Visual did not have a parent before.</param>
protected internal virtual void OnVisualParentChanged(DependencyObject oldParent)
{
}
/// <summary>
/// OnVisualChildrenChanged is called when the VisualCollection of the Visual is edited.
/// </summary>
protected internal virtual void OnVisualChildrenChanged(
DependencyObject visualAdded,
DependencyObject visualRemoved)
{
}
/// <summary>
/// OnDpiChanged is called when the DPI at which this visual is rendered, changes.
/// </summary>
protected virtual void OnDpiChanged(
DpiScale oldDpi,
DpiScale newDpi)
{
}
/// <summary>
/// Add removed delegates to the VisualAncenstorChanged Event.
/// </summary>
/// <remarks>
/// This also sets/clears the tree-searching bit up the tree
/// </remarks>
internal event AncestorChangedEventHandler VisualAncestorChanged
{
add
{
AncestorChangedEventHandler newHandler = AncestorChangedEventField.GetValue(this);
if (newHandler == null)
{
newHandler = value;
}
else
{
newHandler += value;
}
AncestorChangedEventField.SetValue(this, newHandler);
SetTreeBits(
this,
VisualFlags.SubTreeHoldsAncestorChanged,
VisualFlags.RegisteredForAncestorChanged);
}
remove
{
// check that we are Disabling a node that was previously Enabled
if(CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
{
ClearTreeBits(
this,
VisualFlags.SubTreeHoldsAncestorChanged,
VisualFlags.RegisteredForAncestorChanged);
}
// if we are Disabling a Visual that was not Enabled then this
// search should fail. But it is safe to check.
AncestorChangedEventHandler newHandler = AncestorChangedEventField.GetValue(this);
if (newHandler != null)
{
newHandler -= value;
if(newHandler == null)
{
AncestorChangedEventField.ClearValue(this);
}
else
{
AncestorChangedEventField.SetValue(this, newHandler);
}
}
}
}
/// <summary>
/// Walks down in the tree for nodes that have AncestorChanged Handlers
/// registered and calls them.
/// It uses Flag bits that help it prune the walk. This should go
/// straight to the relevent nodes.
/// </summary>
internal static void ProcessAncestorChangedNotificationRecursive(DependencyObject e, AncestorChangedEventArgs args)
{
if (e is Visual3D)
{
Visual3D.ProcessAncestorChangedNotificationRecursive(e, args);
}
else
{
Visual eAsVisual = e as Visual;
// If the flag is not set, then we are Done.
if(!eAsVisual.CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
{
return;
}
// If there is a handler on this node, then fire it.
AncestorChangedEventHandler handler = AncestorChangedEventField.GetValue(eAsVisual);
if(handler != null)
{
handler(eAsVisual, args);
}
// Decend into the children.
int count = eAsVisual.InternalVisual2DOr3DChildrenCount;
for (int i = 0; i < count; i++)
{
DependencyObject childVisual = eAsVisual.InternalGet2DOr3DVisualChild(i);
if (childVisual != null)
{
ProcessAncestorChangedNotificationRecursive(childVisual, args);
}
}
}
}
/// <summary>
/// Returns true if the specified ancestor (this) is really the ancestor of the
/// given descendant (argument).
/// </summary>
public bool IsAncestorOf(DependencyObject descendant)
{
Visual visual;
Visual3D visual3D;
VisualTreeUtils.AsNonNullVisual(descendant, out visual, out visual3D);
// x86 branch prediction skips the branch on first encounter. We favor 2D.
if(visual3D != null)
{
return visual3D.IsDescendantOf(this);
}
return visual.IsDescendantOf(this);
}
/// <summary>
/// Returns true if the refernece Visual (this) is a descendant of the argument Visual.
/// </summary>
public bool IsDescendantOf(DependencyObject ancestor)
{
ArgumentNullException.ThrowIfNull(ancestor);
VisualTreeUtils.EnsureVisual(ancestor);
// Walk up the parent chain of the descendant until we run out
// of 2D parents or we find the ancestor.
DependencyObject current = this;
while ((current != null) && (current != ancestor))
{
Visual currentAsVisual = current as Visual;
if (currentAsVisual != null)
{
current = currentAsVisual._parent;
}
else
{
Visual3D currentAsVisual3D = current as Visual3D;
if (currentAsVisual3D != null)
{
current = currentAsVisual3D.InternalVisualParent;
}
else
{
current = null;
}
}
}
return current == ancestor;
}
/// <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 void SetFlagsToRoot(bool value, VisualFlags flag)
{
Visual current = this;
do
{
current.SetFlags(value, flag);
Visual currentParent = current._parent as Visual;
// if the cast to currentParent failed and yet current._parent is not null then
// we have a 3D element. Call SetFlagsToRoot on it instead.
if (current._parent != null && currentParent == null)
{
((Visual3D)current._parent).SetFlagsToRoot(value, flag);
return;
}
current = currentParent;
}
while (current != null);
}
/// <summary>
/// Finds the first ancestor of the given element which has the given
/// flags set.
/// </summary>
internal DependencyObject FindFirstAncestorWithFlagsAnd(VisualFlags flag)
{
Visual current = this;
do
{
if (current.CheckFlagsAnd(flag))
{
// The other Visual crossed through this Visual's parent chain. Hence this is our
// common ancestor.
return current;
}
DependencyObject parent = current._parent;
// first attempt to see if parent is a Visual, in which case we continue the loop.
// Otherwise see if it's a Visual3D, and call the similar method on it.
current = parent as Visual;
if (current == null)
{
Visual3D parentAsVisual3D = parent as Visual3D;
if (parentAsVisual3D != null)
{
return parentAsVisual3D.FindFirstAncestorWithFlagsAnd(flag);
}
}
}
while (current != null);
return null;
}
/// <summary>
/// Finds the common ancestor of two Visuals.
/// </summary>
/// <returns>Returns the common ancestor if the Visuals have one or otherwise null.</returns>
/// <exception cref="ArgumentNullException">If the argument is null.</exception>
public DependencyObject FindCommonVisualAncestor(DependencyObject otherVisual)
{
VerifyAPIReadOnly(otherVisual);
ArgumentNullException.ThrowIfNull(otherVisual);
// Since we can't rely on code running in the CLR, we need to first make sure
// that the FindCommonAncestor flag is not set. It is enought to ensure this
// on one path to the root Visual.
// Later, when we get from the CLR the "RunForSure" section support, we can replace
// this algorithm with one that is linear in the distance of the two visuals to
// their common ancestor.
SetFlagsToRoot(false, VisualFlags.FindCommonAncestor);
// Walk up the other visual's parent chain and set the FindCommonAncestor flag.
VisualTreeUtils.SetFlagsToRoot(otherVisual, true, VisualFlags.FindCommonAncestor);
// Now see if the other Visual's parent chain crosses our parent chain.
return FindFirstAncestorWithFlagsAnd(VisualFlags.FindCommonAncestor);
}
#endregion Visual Ancestry Relations
#region ForceInherit property support
internal virtual void InvalidateForceInheritPropertyOnChildren(DependencyProperty property)
{
UIElement.InvalidateForceInheritPropertyOnChildren(this, property);
}
#endregion ForceInherit property support
// --------------------------------------------------------------------
//
// Visual-to-Visual Transforms
//
// --------------------------------------------------------------------
#region Visual-to-Visual Transforms
/// <summary>
/// Returns a transform that can be used to transform coordinate from this
/// node to the specified ancestor. It allows 3D to be between the 2D nodes.
/// </summary>
/// <exception cref="ArgumentNullException">
/// If ancestor is null.
/// </exception>
/// <exception cref="ArgumentException">
/// If the ancestor Visual is not a ancestor of Visual.
/// </exception>
/// <exception cref="InvalidOperationException">If the Visuals are not connected.</exception>
public GeneralTransform TransformToAncestor(
Visual ancestor)
{
ArgumentNullException.ThrowIfNull(ancestor);
VerifyAPIReadOnly(ancestor);
return InternalTransformToAncestor(ancestor, false);
}
/// <summary>
/// Returns a transform that can be used to transform coordinate from this
/// node to the specified ancestor.
/// </summary>
/// <exception cref="ArgumentNullException">
/// If ancestor is null.
/// </exception>
/// <exception cref="ArgumentException">
/// If the ancestor Visual3D is not a ancestor of Visual.
/// </exception>
/// <exception cref="InvalidOperationException">If the Visuals are not connected.</exception>
public GeneralTransform2DTo3D TransformToAncestor(Visual3D ancestor)
{
ArgumentNullException.ThrowIfNull(ancestor);
VerifyAPIReadOnly(ancestor);
return InternalTransformToAncestor(ancestor, false);
}
/// <summary>
/// Returns a transform that can be used to transform coordinates from this
/// node to the specified descendant, or null if the transform from descendant to "this"
/// is non-invertible. It allows 3D to be between the 2D nodes.
/// </summary>
/// <exception cref="ArgumentException">
/// If the reference Visual is not a ancestor of the descendant Visual.
/// </exception>
/// <exception cref="ArgumentNullException">If the descendant argument is null.</exception>
/// <exception cref="InvalidOperationException">If the Visuals are not connected.</exception>
public GeneralTransform TransformToDescendant(Visual descendant)
{
ArgumentNullException.ThrowIfNull(descendant);
VerifyAPIReadOnly(descendant);
return descendant.InternalTransformToAncestor(this, true);
}
/// <summary>
/// The returned matrix can be used to transform coordinates from this Visual to
/// the specified Visual.
/// Returns null if no such transform exists due to a non-invertible Transform.
/// </summary>
/// <exception cref="ArgumentNullException">If visual is null.</exception>
/// <exception cref="InvalidOperationException">If the Visuals are not connected.</exception>
public GeneralTransform TransformToVisual(Visual visual)
{
DependencyObject ancestor = FindCommonVisualAncestor(visual);
Visual ancestorAsVisual = ancestor as Visual;
if (ancestorAsVisual == null)
{
throw new System.InvalidOperationException(SR.Visual_NoCommonAncestor);
}
GeneralTransform g0;
Matrix m0;
bool isSimple0 = this.TrySimpleTransformToAncestor(ancestorAsVisual,
false,
out g0,
out m0);
GeneralTransform g1;
Matrix m1;
bool isSimple1 = visual.TrySimpleTransformToAncestor(ancestorAsVisual,
true,
out g1,
out m1);
// combine the transforms
// if both transforms are simple Matrix transforms, just multiply them and
// return the result.
if (isSimple0 && isSimple1)
{
MatrixUtil.MultiplyMatrix(ref m0, ref m1);
MatrixTransform m = new MatrixTransform(m0);
m.Freeze();
return m;
}
// Handle the case where 0 is simple and 1 is complex.
if (isSimple0)
{
g0 = new MatrixTransform(m0);
g0.Freeze();
}
else if (isSimple1)
{
g1 = new MatrixTransform(m1);
g1.Freeze();
}
// If inverse was requested, TrySimpleTransformToAncestor can return null
// add the transform only if it is not null
if (g1 != null)
{
GeneralTransformGroup group = new GeneralTransformGroup();
group.Children.Add(g0);
group.Children.Add(g1);
group.Freeze();
return group;
}
return g0;
}
/// <summary>
/// Returns the transform or the inverse transform between this visual and the specified ancestor.
/// If inverse is requested but does not exist (if the transform is not invertible), null is returned.
/// </summary>
/// <param name="ancestor">Ancestor visual.</param>
/// <param name="inverse">Returns inverse if this argument is true.</param>
private GeneralTransform InternalTransformToAncestor(Visual ancestor, bool inverse)
{
GeneralTransform generalTransform;
Matrix simpleTransform;
bool isSimple = TrySimpleTransformToAncestor(ancestor,
inverse,
out generalTransform,
out simpleTransform);
if (isSimple)
{
MatrixTransform matrixTransform = new MatrixTransform(simpleTransform);
matrixTransform.Freeze();
return matrixTransform;
}
else
{
return generalTransform;
}
}
/// <summary>
/// Provides the transform or the inverse transform between this visual and the specified ancestor.
/// Returns true if the transform is "simple" - in which case the GeneralTransform is null
/// and the caller should use the Matrix.
/// Otherwise, returns false - use the GeneralTransform and ignore the Matrix.
/// If inverse is requested but not available (if the transform is not invertible), false is
/// returned and the GeneralTransform is null.
/// </summary>
/// <param name="ancestor">Ancestor visual.</param>
/// <param name="inverse">Returns inverse if this argument is true.</param>
/// <param name="generalTransform">The GeneralTransform if this method returns false.</param>
/// <param name="simpleTransform">The Matrix if this method returns true.</param>
internal bool TrySimpleTransformToAncestor(Visual ancestor,
bool inverse,
out GeneralTransform generalTransform,
out Matrix simpleTransform)
{
Debug.Assert(ancestor != null);
// flag to indicate if we have a case where we do multile 2D->3D->2D transitions
bool embedded2Don3D = false;
DependencyObject g = this;
Matrix m = Matrix.Identity;
// Keep this null until it's needed
GeneralTransformGroup group = null;
// This while loop will walk up the visual tree until we encounter the ancestor.
// As it does so, it will accumulate the descendent->ancestor transform.
// In most cases, this is simply a matrix, though if we encounter a bitmap effect we
// will need to use a general transform group to store the transform.
// We will accumulate the current transform in a matrix until we encounter a bitmap effect,
// at which point we will add the matrix's current value and the bitmap effect's transforms
// to the GeneralTransformGroup and continue to accumulate further transforms in the matrix again.
// At the end of this loop, we will have 0 or more transforms in the GeneralTransformGroup
// and the matrix which, if not identity, should be appended to the GeneralTransformGroup.
// If, as is commonly the case, this loop terminates without encountering a bitmap effect
// we will simply use the Matrix.
while ((VisualTreeHelper.GetParent(g) != null) && (g != ancestor))
{
Visual gAsVisual = g as Visual;
if (gAsVisual != null)
{
if (gAsVisual.CheckFlagsAnd(VisualFlags.NodeHasEffect))
{
// Only check for Effect, not legacy BitmapEffect. Previous
// version had an incorrect BitmapEffect implementation
// here, and there's no need to improve on our
// BitmapEffect implementation if it didn't work
// before.
Effect imageEffect = EffectField.GetValue(gAsVisual);
if (imageEffect != null)
{
GeneralTransform gt = imageEffect.CoerceToUnitSpaceGeneralTransform(
imageEffect.EffectMapping,
gAsVisual.VisualDescendantBounds);
Transform affineTransform = gt.AffineTransform;
if (affineTransform != null)
{
Matrix cm = affineTransform.Value;
MatrixUtil.MultiplyMatrix(ref m, ref cm);
}
else
{
if (group == null)
{
group = new GeneralTransformGroup();
}
group.Children.Add(new MatrixTransform(m));
m = Matrix.Identity;
group.Children.Add(gt);
}
}
}
Transform transform = TransformField.GetValue(gAsVisual);
if (transform != null)
{
Matrix cm = transform.Value;
MatrixUtil.MultiplyMatrix(ref m, ref cm);
}
m.Translate(gAsVisual._offset.X, gAsVisual._offset.Y); // Consider having a bit that indicates that we have a non-null offset.
g = gAsVisual._parent;
}
else
{
// we just hit a Visual3D - use a GeneralTransform to go from 2D -> 3D -> 2D
// and then return to the tree using the 2D parent - the general transform will deal with the
// actual transformation. This Visual3D also must be a Viewport2DVisual3D since this is the only
// Visual3D that can have a 2D child.
Viewport2DVisual3D gAsVisual3D = g as Viewport2DVisual3D;
if (group == null)
{
group = new GeneralTransformGroup();
}
group.Children.Add(new MatrixTransform(m));
m = Matrix.Identity;
Visual visualForGenTransform = null;
if (embedded2Don3D)
{
visualForGenTransform = gAsVisual3D.Visual;
}
else
{
visualForGenTransform = this;
embedded2Don3D = true;
}
group.Children.Add(new GeneralTransform2DTo3DTo2D(gAsVisual3D, visualForGenTransform));
g = VisualTreeHelper.GetContainingVisual2D(gAsVisual3D);
}
}
if (g != ancestor)
{
throw new System.InvalidOperationException(inverse ? SR.Visual_NotADescendant : SR.Visual_NotAnAncestor);
}
// At this point, we will have 0 or more transforms in the GeneralTransformGroup
// and the matrix which, if not identity, should be appended to the GeneralTransformGroup.
// If, as is commonly the case, this loop terminates without encountering a bitmap effect
// we will simply use the Matrix.
// Assert that a non-null group implies at least one child
Debug.Assert((group == null) || (group.Children.Count > 0));
// Do we have a group?
if (group != null)
{
if (!m.IsIdentity)
{
group.Children.Add(new MatrixTransform(m));
}
if (inverse)
{
group = (GeneralTransformGroup)group.Inverse;
}
// group can be null if it does not have an inverse
if (group != null)
{
group.Freeze();
}
// Initialize out params
generalTransform = group;
simpleTransform = new Matrix();
return false; // simple transform failed
}
// If not, the entire transform is stored in the matrix
else
{
// Initialize out params
generalTransform = null;
if (inverse)
{
if (!m.HasInverse)
{
simpleTransform = new Matrix();
return false; // inversion failed, so simple transform failed.
}
m.Invert();
}
simpleTransform = m;
return true; // simple transform succeeded
}
}
/// <summary>
/// Returns the transform or the inverse transform between this visual and the specified ancestor.
/// If inverse is requested but does not exist (if the transform is not invertible), null is returned.
/// </summary>
/// <param name="ancestor">Ancestor visual.</param>
/// <param name="inverse">Returns inverse if this argument is true.</param>
private GeneralTransform2DTo3D InternalTransformToAncestor(Visual3D ancestor, bool inverse)
{
GeneralTransform2DTo3D transformTo3D = null;
if (TrySimpleTransformToAncestor(ancestor,
out transformTo3D))
{
transformTo3D.Freeze();
return transformTo3D;
}
else
{
return null;
}
}
/// <summary>
/// Provides the transform to go from 2D to 3D.
/// </summary>
/// <param name="ancestor">Ancestor visual.</param>
/// <param name="transformTo3D">The transform to use to go to 3D</param>
internal bool TrySimpleTransformToAncestor(Visual3D ancestor,
out GeneralTransform2DTo3D transformTo3D)
{
Debug.Assert(ancestor != null);
// get the 3D object that contains this visual
// this must be a Viewport2DVisual3D since this is the only 3D class that can contain 2D content as a child
Viewport2DVisual3D containingVisual3D = VisualTreeHelper.GetContainingVisual3D(this) as Viewport2DVisual3D;
// if containingVisual3D is null then ancestor is not the ancestor
if (containingVisual3D == null)
{
throw new System.InvalidOperationException(SR.Visual_NotAnAncestor);
}
GeneralTransform transform2D = this.TransformToAncestor(containingVisual3D.Visual);
GeneralTransform3D transform3D = containingVisual3D.TransformToAncestor(ancestor);
transformTo3D = new GeneralTransform2DTo3D(transform2D, containingVisual3D, transform3D);
return true;
}
/// <summary>
/// Returns the DPI information at which this Visual is rendered.
/// </summary>
internal DpiScale GetDpi()
{
DpiScale dpi;
lock (UIElement.DpiLock)
{
if (UIElement.DpiScaleXValues.Count == 0)
{
// This is for scenarios where an HWND hasn't been created yet.
return UIElement.EnsureDpiScale();
}
// initialized to system DPI as a fallback value
dpi = new DpiScale(UIElement.DpiScaleXValues[0], UIElement.DpiScaleYValues[0]);
int index = 0;
index = CheckFlagsAnd(VisualFlags.DpiScaleFlag1) ? index | 1 : index;
index = CheckFlagsAnd(VisualFlags.DpiScaleFlag2) ? index | 2 : index;
if (index < 3 && UIElement.DpiScaleXValues[index] != 0 && UIElement.DpiScaleYValues[index] != 0)
{
dpi = new DpiScale(UIElement.DpiScaleXValues[index], UIElement.DpiScaleYValues[index]);
}
else if (index >= 3)
{
int actualIndex = DpiIndex.GetValue(this);
dpi = new DpiScale(UIElement.DpiScaleXValues[actualIndex], UIElement.DpiScaleYValues[actualIndex]);
}
}
return dpi;
}
/// <summary>
/// This method converts a point in the current Visual's coordinate
/// system into a point in screen coordinates.
/// </summary>
public Point PointToScreen(Point point)
{
VerifyAPIReadOnly();
PresentationSource inputSource = PresentationSource.FromVisual(this);
if (inputSource == null)
{
throw new InvalidOperationException(SR.Visual_NoPresentationSource);
}
// Translate the point from the visual to the root.
GeneralTransform gUp = this.TransformToAncestor(inputSource.RootVisual);
if (gUp == null || !gUp.TryTransform(point, out point))
{
throw new InvalidOperationException(SR.Visual_CannotTransformPoint);
}
// Translate the point from the root to the screen
point = PointUtil.RootToClient(point, inputSource);
point = PointUtil.ClientToScreen(point, inputSource);
return point;
}
/// <summary>
/// This method converts a point in screen coordinates into a point
/// in the current Visual's coordinate system.
/// </summary>
public Point PointFromScreen(Point point)
{
VerifyAPIReadOnly();
PresentationSource inputSource = PresentationSource.FromVisual(this);
if (inputSource == null)
{
throw new InvalidOperationException(SR.Visual_NoPresentationSource);
}
// Translate the point from the screen to the root
point = PointUtil.ScreenToClient(point, inputSource);
point = PointUtil.ClientToRoot(point, inputSource);
// Translate the point from the root to the visual.
GeneralTransform gDown = inputSource.RootVisual.TransformToDescendant(this);
if (gDown == null || !gDown.TryTransform(point, out point))
{
throw new InvalidOperationException(SR.Visual_CannotTransformPoint);
}
return point;
}
#endregion Visual-to-Visual Transforms
// --------------------------------------------------------------------
//
// Internal Event Handlers
//
// --------------------------------------------------------------------
#region Internal Event Handlers
internal EventHandler ClipChangedHandler
{
get
{
return new EventHandler(ClipChanged);
}
}
internal void ClipChanged(object sender, EventArgs e)
{
PropagateChangedFlags();
}
internal EventHandler ScrollableAreaClipChangedHandler
{
get
{
return new EventHandler(ScrollableAreaClipChanged);
}
}
internal void ScrollableAreaClipChanged(object sender, EventArgs e)
{
PropagateChangedFlags();
}
internal EventHandler TransformChangedHandler
{
get
{
return new EventHandler(TransformChanged);
}
}
internal void TransformChanged(object sender, EventArgs e)
{
PropagateChangedFlags();
}
internal EventHandler EffectChangedHandler
{
get
{
return new EventHandler(EffectChanged);
}
}
internal void EffectChanged(object sender, EventArgs e)
{
PropagateChangedFlags();
}
internal EventHandler CacheModeChangedHandler
{
get
{
return new EventHandler(EffectChanged);
}
}
internal void CacheModeChanged(object sender, EventArgs e)
{
PropagateChangedFlags();
}
internal EventHandler GuidelinesChangedHandler
{
get
{
return new EventHandler(GuidelinesChanged);
}
}
internal void GuidelinesChanged(object sender, EventArgs e)
{
SetFlagsOnAllChannels(
true,
VisualProxyFlags.IsGuidelineCollectionDirty);
PropagateChangedFlags();
}
internal EventHandler OpacityMaskChangedHandler
{
get
{
return new EventHandler(OpacityMaskChanged);
}
}
internal void OpacityMaskChanged(object sender, EventArgs e)
{
PropagateChangedFlags();
}
internal EventHandler ContentsChangedHandler
{
get
{
return new EventHandler(ContentsChanged);
}
}
internal virtual void ContentsChanged(object sender, EventArgs e)
{
PropagateChangedFlags();
}
#endregion Internal Event Handlers
// --------------------------------------------------------------------
//
// Visual flags manipulation
//
// --------------------------------------------------------------------
#region Visual flags manipulation
/// <summary>
/// SetFlagsOnAllChannels is used to set or unset one
/// or multiple flags on all channels this visual is
/// marshaled to.
/// </summary>
internal void SetFlagsOnAllChannels(
bool value,
VisualProxyFlags flagsToChange)
{
_proxy.SetFlagsOnAllChannels(
value,
flagsToChange);
}
/// <summary>
/// SetFlags is used to set or unset one or multiple flags on a given channel.
/// </summary>
internal void SetFlags(
DUCE.Channel channel,
bool value,
VisualProxyFlags flagsToChange)
{
_proxy.SetFlags(
channel,
value,
flagsToChange);
}
/// <summary>
/// SetFlags is used to set or unset one or multiple node flags on the node.
/// </summary>
internal void SetFlags(bool value, VisualFlags flags)
{
_flags = value ? (_flags | flags) : (_flags & (~flags));
}
/// <summary>
/// Sets the DPI scale Visual flags on the current visual.
/// </summary>
internal void SetDpiScaleVisualFlags(DpiRecursiveChangeArgs args)
{
_flags = args.DpiScaleFlag1 ? (_flags | VisualFlags.DpiScaleFlag1) : (_flags & ~VisualFlags.DpiScaleFlag1);
_flags = args.DpiScaleFlag2 ? (_flags | VisualFlags.DpiScaleFlag2) : (_flags & ~VisualFlags.DpiScaleFlag2);
if (args.DpiScaleFlag1 && args.DpiScaleFlag2)
{
DpiIndex.SetValue(this, args.Index);
}
if (!args.OldDpiScale.Equals(args.NewDpiScale))
{
OnDpiChanged(args.OldDpiScale, args.NewDpiScale);
}
}
/// <summary>
/// Recursively sets the DPI scale visual flags.
/// </summary>
internal void RecursiveSetDpiScaleVisualFlags(DpiRecursiveChangeArgs args)
{
SetDpiScaleVisualFlags(args);
int count = InternalVisualChildrenCount;
for (int i = 0; i < count; i++)
{
Visual cv = InternalGetVisualChild(i);
if (cv != null)
{
cv.RecursiveSetDpiScaleVisualFlags(args);
}
}
}
/// <summary>
/// CheckFlagsOnAllChannels returns true if all flags in
/// the bitmask flags are set on all channels this visual is
/// marshaled to.
/// </summary>
/// <remarks>
/// If there aren't any bits set on the specified flags
/// the method returns true.
/// </remarks>
internal bool CheckFlagsOnAllChannels(VisualProxyFlags flagsToCheck)
{
return _proxy.CheckFlagsOnAllChannels(flagsToCheck);
}
/// <summary>
/// CheckFlagsAnd returns true if all flags in the bitmask flags
/// are set on a given channel.
/// </summary>
/// <remarks>
/// If there aren't any bits set on the specified flags
/// the method returns true.
/// </remarks>
internal bool CheckFlagsAnd(
DUCE.Channel channel,
VisualProxyFlags flagsToCheck)
{
return (_proxy.GetFlags(channel) & flagsToCheck) == flagsToCheck;
}
/// <summary>
/// CheckFlagsAnd returns true if all flags in the bitmask flags are set on the node.
/// </summary>
/// <remarks>If there aren't any bits set on the specified flags the method
/// returns true</remarks>
internal bool CheckFlagsAnd(VisualFlags flags)
{
return (_flags & flags) == flags;
}
/// <summary>
/// Checks if any of the specified flags is set on a given channel.
/// </summary>
/// <remarks>
/// If there aren't any bits set on the specified flags
/// the method returns false.
/// </remarks>
internal bool CheckFlagsOr(
DUCE.Channel channel,
VisualProxyFlags flagsToCheck)
{
return (_proxy.GetFlags(channel) & flagsToCheck) != VisualProxyFlags.None;
}
/// <summary>
/// Checks if any of the specified flags is set on the node.
/// </summary>
/// <remarks>If there aren't any bits set on the specified flags the method
/// returns true</remarks>
internal bool CheckFlagsOr(VisualFlags flags)
{
return (flags == 0) || ((_flags & flags) > 0);
}
/// <summary>
/// Set a bit in a Visual node and in all its direct ancestors.
/// </summary>
/// <param name="e">The Visual Element</param>
/// <param name="treeFlag">The Flag that marks a sub tree to search</param>
/// <param name="nodeFlag">The Flag that marks the node to search for.</param>
internal static void SetTreeBits(
DependencyObject e,
VisualFlags treeFlag,
VisualFlags nodeFlag)
{
Visual eAsVisual;
Visual3D eAsVisual3D;
if (e != null)
{
eAsVisual = e as Visual;
if (eAsVisual != null)
{
eAsVisual.SetFlags(true, nodeFlag);
}
else
{
((Visual3D)e).SetFlags(true, nodeFlag);
}
}
while (null!=e)
{
eAsVisual = e as Visual;
if (eAsVisual != null)
{
// if the bit is already set, then we're done.
if(eAsVisual.CheckFlagsAnd(treeFlag))
return;
eAsVisual.SetFlags(true, treeFlag);
}
else
{
eAsVisual3D = e as Visual3D;
// if the bit is already set, then we're done.
if(eAsVisual3D.CheckFlagsAnd(treeFlag))
return;
eAsVisual3D.SetFlags(true, treeFlag);
}
e = VisualTreeHelper.GetParent(e);
}
}
/// <summary>
/// Clean a bit in a Visual node and in all its direct ancestors;
/// unless the ancestor also has
/// </summary>
/// <param name="e">The Visual Element</param>
/// <param name="treeFlag">The Flag that marks a sub tree to search</param>
/// <param name="nodeFlag">The Flag that marks the node to search for.</param>
internal static void ClearTreeBits(
DependencyObject e,
VisualFlags treeFlag,
VisualFlags nodeFlag)
{
Visual eAsVisual;
Visual3D eAsVisual3D;
// This bit might not be set, but checking costs as much as setting
// So it is faster to just clear it everytime.
if (e != null)
{
eAsVisual = e as Visual;
if (eAsVisual != null)
{
eAsVisual.SetFlags(false, nodeFlag);
}
else
{
((Visual3D)e).SetFlags(false, nodeFlag);
}
}
while (e != null)
{
eAsVisual = e as Visual;
if (eAsVisual != null)
{
if(eAsVisual.CheckFlagsAnd(nodeFlag))
{
return; // Done; if a parent also has the Node bit set.
}
if(DoAnyChildrenHaveABitSet(eAsVisual, treeFlag))
{
return; // Done; if a other subtrees are set.
}
eAsVisual.SetFlags(false, treeFlag);
}
else
{
eAsVisual3D = e as Visual3D;
if(eAsVisual3D.CheckFlagsAnd(nodeFlag))
{
return; // Done; if a parent also has the Node bit set.
}
if(Visual3D.DoAnyChildrenHaveABitSet(eAsVisual3D, treeFlag))
{
return; // Done; if a other subtrees are set.
}
eAsVisual3D.SetFlags(false, treeFlag);
}
e = VisualTreeHelper.GetParent(e);
}
}
/// <summary>
/// Check all the children for a bit.
/// </summary>
private static bool DoAnyChildrenHaveABitSet(
Visual pe,
VisualFlags flag)
{
int count = pe.VisualChildrenCount;
for (int i = 0; i < count; i++)
{
Visual v = pe.GetVisualChild(i);
if (v != null && v.CheckFlagsAnd(flag))
{
return true;
}
}
return false;
}
/// <summary>
/// Propagates the flags up to the root.
/// </summary>
/// <remarks>
/// The walk stops on a node with all of the required flags set.
/// </remarks>
internal static void PropagateFlags(
Visual e,
VisualFlags flags,
VisualProxyFlags proxyFlags)
{
while ((e != null) &&
(!e.CheckFlagsAnd(flags) || !e.CheckFlagsOnAllChannels(proxyFlags)))
{
if (e.CheckFlagsOr(VisualFlags.ShouldPostRender))
{
MediaContext mctx = MediaContext.From(e.Dispatcher);
if (mctx.Channel != null)
{
mctx.PostRender();
}
}
else if (e.CheckFlagsAnd(VisualFlags.NodeIsCyclicBrushRoot))
{
//
// For visuals that are root nodes in visual brushes we
// need to fire OnChanged on the owning brushes.
//
Dictionary<ICyclicBrush, int> cyclicBrushToChannelsMap =
CyclicBrushToChannelsMapField.GetValue(e);
Debug.Assert(cyclicBrushToChannelsMap != null, "Visual brush roots need to have the visual brush to channels map!");
//
// Iterate over the visual brushes and fire the OnChanged event.
//
foreach (ICyclicBrush cyclicBrush in cyclicBrushToChannelsMap.Keys)
{
cyclicBrush.FireOnChanged();
}
}
e.SetFlags(true, flags);
e.SetFlagsOnAllChannels(true, proxyFlags);
if (e._parent == null)
{
// Stop propagating. We are at the root of the 2D subtree.
return;
}
Visual parentAsVisual = e._parent as Visual;
if (parentAsVisual == null)
{
// if the parent is not null (saw this with earlier null check) and is not a Visual
// it must be a Visual3D - continue the propagation
Visual3D.PropagateFlags((Visual3D)e._parent, flags, proxyFlags);
return;
}
e = parentAsVisual;
}
}
/// <summary>
/// Propagates the dirty flags up to the root.
/// </summary>
/// <remarks>
/// The walk stops on a node with all of the required flags set.
/// </remarks>
internal void PropagateChangedFlags()
{
PropagateFlags(
this,
VisualFlags.IsSubtreeDirtyForPrecompute,
VisualProxyFlags.IsSubtreeDirtyForRender);
}
private bool NodeHasLegacyBitmapEffect
{
get
{
// NodeHasEffect flag is overloaded for both legacy
// BitmapEffects and the newer Effects
return
CheckFlagsAnd(VisualFlags.NodeHasEffect) &&
BitmapEffectStateField.GetValue(this) != null;
}
}
#endregion Visual flags manipulation
// --------------------------------------------------------------------
//
// Internal Fields
//
// --------------------------------------------------------------------
#region Internal Fields
internal static readonly UncommonField<BitmapEffectState> BitmapEffectStateField = new UncommonField<BitmapEffectState>();
internal delegate void AncestorChangedEventHandler(object sender, AncestorChangedEventArgs e);
// index in parent child array. no meaning if parent is null.
// note that we maintain in debug that the _parentIndex is -1 if the parent is null.
// Exception: children added to TextBoxView and InkPresenter.
internal int _parentIndex;
// We may have to change the API so that we can save
// here. For now that is good enough.
internal DependencyObject _parent;
internal VisualProxy _proxy;
#endregion Internal Fields
// --------------------------------------------------------------------
//
// Private Fields
//
// --------------------------------------------------------------------
#region Private Fields
// bbox in inner coordinate space of this node including its children.
private Rect _bboxSubgraph = Rect.Empty;
//
// Store the cyclic brushes that hold on to this visual. Also store the corresponding
// number of channel, on which that cyclic brush holds on to this visual.
//
private static readonly UncommonField<Dictionary<ICyclicBrush, int>> CyclicBrushToChannelsMapField
= new UncommonField<Dictionary<ICyclicBrush, int>>();
//
// Store the channels on which cyclic brushes hold on to this visual. Also store the
// corresponding number of cyclic brushes on that channel, holding on to this visual.
//
private static readonly UncommonField<Dictionary<DUCE.Channel, int>> ChannelsToCyclicBrushMapField
= new UncommonField<Dictionary<DUCE.Channel, int>>();
internal static readonly UncommonField<int> DpiIndex = new UncommonField<int>();
private static readonly UncommonField<Geometry> ClipField = new UncommonField<Geometry>();
private static readonly UncommonField<double> OpacityField = new UncommonField<double>(1.0);
private static readonly UncommonField<Brush> OpacityMaskField = new UncommonField<Brush>();
private static readonly UncommonField<EdgeMode> EdgeModeField = new UncommonField<EdgeMode>();
private static readonly UncommonField<BitmapScalingMode> BitmapScalingModeField = new UncommonField<BitmapScalingMode>();
private static readonly UncommonField<ClearTypeHint> ClearTypeHintField = new UncommonField<ClearTypeHint>();
private static readonly UncommonField<Transform> TransformField = new UncommonField<Transform>();
private static readonly UncommonField<Effect> EffectField = new UncommonField<Effect>();
private static readonly UncommonField<CacheMode> CacheModeField = new UncommonField<CacheMode>();
private static readonly UncommonField<DoubleCollection> GuidelinesXField = new UncommonField<DoubleCollection>();
private static readonly UncommonField<DoubleCollection> GuidelinesYField = new UncommonField<DoubleCollection>();
private static readonly UncommonField<AncestorChangedEventHandler> AncestorChangedEventField
= new UncommonField<AncestorChangedEventHandler>();
private static readonly UncommonField<BitmapEffectState> UserProvidedBitmapEffectData = new UncommonField<BitmapEffectState>();
private static readonly UncommonField<Rect?> ScrollableAreaClipField = new UncommonField<Rect?>(null);
private static readonly UncommonField<TextRenderingMode> TextRenderingModeField = new UncommonField<TextRenderingMode>();
private static readonly UncommonField<TextHintingMode> TextHintingModeField = new UncommonField<TextHintingMode>();
private Vector _offset;
private VisualFlags _flags;
private const uint TreeLevelLimit = 0x7FF;
#endregion Private Fields
}
}
|