/// <summary> /// Set the visual parent to a child. /// </summary> /// <param name="child">The child to which set the visual parent.</param> /// <param name="parent">The parent of the child.</param> protected static void SetVisualParent(UIElement child, UIElement parent) { if (child == null) throw new ArgumentNullException(nameof(child)); if (parent != null && child.VisualParent != null && parent != child.VisualParent) throw new InvalidOperationException("The UI element 'Name=" + child.Name + "' has already as visual parent the element 'Name=" + child.VisualParent.Name + "'."); child.VisualParent?.VisualChildrenCollection.Remove(child); child.VisualParent = parent; if (parent != null) { child.ResourceDictionary = parent.ResourceDictionary; child.LayoutingContext = parent.layoutingContext; parent.VisualChildrenCollection.Add(child); } }
/// <summary> /// Propagate the collapsing to a child element <paramref name="element"/>. /// </summary> /// <param name="element">A child element to which propagate the collapse.</param> /// <exception cref="InvalidOperationException"><paramref name="element"/> is not a child of this element.</exception> protected void PropagateCollapseToChild(UIElement element) { if (element.VisualParent != this) throw new InvalidOperationException("Element is not a child of this element."); element.InvalidateMeasure(); element.CollapseOverride(); }
/// <summary> /// Set the parent to a child. /// </summary> /// <param name="child">The child to which set the parent.</param> /// <param name="parent">The parent of the child.</param> protected static void SetParent(UIElement child, UIElement parent) { if (parent != null && child.Parent != null && parent != child.Parent) throw new InvalidOperationException("The UI element 'Name="+child.Name+"' has already as parent the element 'Name="+child.Parent.Name+"'."); child.Parent = parent; }
public ElementRenderer GetRenderer(UIElement element) { return rendererManager.GetRenderer(element); }
public void RegisterRenderer(UIElement element, ElementRenderer renderer) { rendererManager.RegisterRenderer(element, renderer); }
private UIElement GetElementAtScreenPosition(UIElement rootElement, Vector2 position, ref Vector3 intersectionPoint) { // here we use a trick to take into the calculation the viewport => we multiply the screen position by the viewport ratio (easier than modifying the view matrix) var positionForHitTest = Vector2.Demodulate(position, viewportTargetRatio) - new Vector2(0.5f); // calculate the ray corresponding to the click var rayDirectionView = Vector3.Normalize(new Vector3(positionForHitTest.X * viewParameters.FrustumHeight * viewParameters.AspectRatio, -positionForHitTest.Y * viewParameters.FrustumHeight, -1)); var clickRay = new Ray(viewParameters.ViewMatrixInverse.TranslationVector, Vector3.TransformNormal(rayDirectionView, viewParameters.ViewMatrixInverse)); // perform the hit test UIElement clickedElement = null; var smallestDepth = float.PositiveInfinity; PerformRecursiveHitTest(rootElement, ref clickRay, ref clickedElement, ref intersectionPoint, ref smallestDepth); return clickedElement; }
private void PerformRecursiveHitTest(UIElement element, ref Ray ray, ref UIElement clickedElement, ref Vector3 intersectionPoint, ref float smallestDepth) { // if the element is not visible, we also remove all its children if (!element.IsVisible) return; if (element.ClipToBounds || element.CanBeHitByUser) { Vector3 intersection; var intersect = element.Intersects(ref ray, out intersection); // don't perform the hit test on children if clipped and parent no hit if (element.ClipToBounds && !intersect) return; // Calculate the depth of the element with the depth bias so that hit test corresponds to visuals. Vector4 projectedIntersection; var intersection4 = new Vector4(intersection, 1); Vector4.Transform(ref intersection4, ref viewParameters.ViewProjectionMatrix, out projectedIntersection); var depthWithBias = projectedIntersection.Z / projectedIntersection.W - element.DepthBias * BatchBase<int>.DepthBiasShiftOneUnit; // update the closest element hit if (element.CanBeHitByUser && intersect && depthWithBias < smallestDepth) { smallestDepth = depthWithBias; intersectionPoint = intersection; clickedElement = element; } } // render the children foreach (var child in element.HitableChildren) PerformRecursiveHitTest(child, ref ray, ref clickedElement, ref intersectionPoint, ref smallestDepth); }
private UIElement FindCommonParent(UIElement element1, UIElement element2) { // build the list of the parents of the newly selected element newlySelectedElementParents.Clear(); var newElementParent = element1; while (newElementParent != null) { newlySelectedElementParents.Add(newElementParent); newElementParent = newElementParent.VisualParent; } // find the common element into the previously and newly selected element hierarchy var commonElement = element2; while (commonElement != null && !newlySelectedElementParents.Contains(commonElement)) commonElement = commonElement.VisualParent; return commonElement; }
private void ThrowEnterAndLeaveTouchEvents(UIElement currentElement, UIElement previousElement, TouchEventArgs touchEvent) { var commonElement = FindCommonParent(currentElement, previousElement); // raise leave events to the hierarchy: previousElt -> commonElementParent var previousElementParent = previousElement; while (previousElementParent != commonElement && previousElementParent != null) { if (previousElementParent.IsHierarchyEnabled && previousElementParent.IsTouched) { touchEvent.Handled = false; // reset 'handled' because it corresponds to another event previousElementParent.RaiseTouchLeaveEvent(touchEvent); } previousElementParent = previousElementParent.VisualParent; } // raise enter events to the hierarchy: newElt -> commonElementParent var newElementParent = currentElement; while (newElementParent != commonElement && newElementParent != null) { if (newElementParent.IsHierarchyEnabled && !newElementParent.IsTouched) { touchEvent.Handled = false; // reset 'handled' because it corresponds to another event newElementParent.RaiseTouchEnterEvent(touchEvent); } newElementParent = newElementParent.VisualParent; } }
public HitTestResult(float depthBias, UIElement element, Vector3 intersection) { DepthBias = depthBias; Element = element; IntersectionPoint = intersection; }
private void ReccursiveDrawWithClipping(RenderContext context, UIElement element) { // if the element is not visible, we also remove all its children if (!element.IsVisible) return; var renderer = rendererManager.GetRenderer(element); renderingContext.DepthBias = element.DepthBias; // render the clipping region of the element if (element.ClipToBounds) { // flush current elements batch.End(); // render the clipping region batch.Begin(ref viewParameters.ViewProjectionMatrix, context.GraphicsDevice.BlendStates.ColorDisabled, uiSystem.IncreaseStencilValueState, renderingContext.StencilTestReferenceValue); renderer.RenderClipping(element, renderingContext); batch.End(); // update context and restart the batch renderingContext.StencilTestReferenceValue += 1; batch.Begin(ref viewParameters.ViewProjectionMatrix, context.GraphicsDevice.BlendStates.AlphaBlend, uiSystem.KeepStencilValueState, renderingContext.StencilTestReferenceValue); } // render the design of the element renderer.RenderColor(element, renderingContext); // render the children foreach (var child in element.VisualChildrenCollection) ReccursiveDrawWithClipping(context, child); // clear the element clipping region from the stencil buffer if (element.ClipToBounds) { // flush current elements batch.End(); renderingContext.DepthBias = element.MaxChildrenDepthBias; // render the clipping region batch.Begin(ref viewParameters.ViewProjectionMatrix, context.GraphicsDevice.BlendStates.ColorDisabled, uiSystem.DecreaseStencilValueState, renderingContext.StencilTestReferenceValue); renderer.RenderClipping(element, renderingContext); batch.End(); // update context and restart the batch renderingContext.StencilTestReferenceValue -= 1; batch.Begin(ref viewParameters.ViewProjectionMatrix, context.GraphicsDevice.BlendStates.AlphaBlend, uiSystem.KeepStencilValueState, renderingContext.StencilTestReferenceValue); } }
private static void PerformRecursiveHitTest(UIElement element, ref Ray ray, ref Matrix worldViewProj, ICollection<HitTestResult> results) { // if the element is not visible, we also remove all its children if (!element.IsVisible) return; var canBeHit = element.CanBeHitByUser; if (canBeHit || element.ClipToBounds) { Vector3 intersection; var intersect = element.Intersects(ref ray, out intersection); // don't perform the hit test on children if clipped and parent no hit if (element.ClipToBounds && !intersect) return; // Calculate the depth of the element with the depth bias so that hit test corresponds to visuals. Vector4 projectedIntersection; var intersection4 = new Vector4(intersection, 1); Vector4.Transform(ref intersection4, ref worldViewProj, out projectedIntersection); // update the hit results if (canBeHit && intersect) { results.Add(new HitTestResult(element.DepthBias, element, intersection)); } } // test the children foreach (var child in element.HitableChildren) PerformRecursiveHitTest(child, ref ray, ref worldViewProj, results); }
private static void PerformRecursiveHitTest(UIElement element, ref Ray ray, ref Matrix worldViewProj, ref UIElement hitElement, ref Vector3 intersectionPoint, ref float smallestDepth, ref float highestDepthBias) { // if the element is not visible, we also remove all its children if (!element.IsVisible) return; var canBeHit = element.CanBeHitByUser; if (canBeHit || element.ClipToBounds) { Vector3 intersection; var intersect = element.Intersects(ref ray, out intersection); // don't perform the hit test on children if clipped and parent no hit if (element.ClipToBounds && !intersect) return; // Calculate the depth of the element with the depth bias so that hit test corresponds to visuals. Vector4 projectedIntersection; var intersection4 = new Vector4(intersection, 1); Vector4.Transform(ref intersection4, ref worldViewProj, out projectedIntersection); var depth = projectedIntersection.Z/projectedIntersection.W; // update the closest element hit if (canBeHit && intersect) { if (depth < smallestDepth || (depth == smallestDepth && element.DepthBias > highestDepthBias)) { smallestDepth = depth; highestDepthBias = element.DepthBias; intersectionPoint = intersection; hitElement = element; } } } // test the children foreach (var child in element.HitableChildren) PerformRecursiveHitTest(child, ref ray, ref worldViewProj, ref hitElement, ref intersectionPoint, ref smallestDepth, ref highestDepthBias); }
/// <summary> /// Gets all elements that the given <paramref name="ray"/> intersects. /// </summary> /// <param name="rootElement">The root <see cref="UIElement"/> from which it should test</param> /// <param name="ray"><see cref="Ray"/> from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range</param> /// <param name="worldViewProj"></param> /// <returns>A collection of all elements hit by this ray, or an empty collection if no hit.</returns> public static ICollection<HitTestResult> GetElementsAtPosition(UIElement rootElement, ref Ray ray, ref Matrix worldViewProj) { var results = new List<HitTestResult>(); PerformRecursiveHitTest(rootElement, ref ray, ref worldViewProj, results); return results; }
/// <summary> /// Gets the element with which the clickRay intersects, or null if none is found /// </summary> /// <param name="rootElement">The root <see cref="UIElement"/> from which it should test</param> /// <param name="clickRay"><see cref="Ray"/> from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range</param> /// <param name="worldViewProj"></param> /// <param name="intersectionPoint">Intersection point between the ray and the element</param> /// <returns>The <see cref="UIElement"/> with which the ray intersects</returns> public static UIElement GetElementAtScreenPosition(UIElement rootElement, ref Ray clickRay, ref Matrix worldViewProj, ref Vector3 intersectionPoint) { UIElement clickedElement = null; var smallestDepth = float.PositiveInfinity; var highestDepthBias = -1.0f; PerformRecursiveHitTest(rootElement, ref clickRay, ref worldViewProj, ref clickedElement, ref intersectionPoint, ref smallestDepth, ref highestDepthBias); return clickedElement; }
// Create a button that can toggle on/off the light shadow map private Button CreateShadowButton(UIElement parentButton, LightComponent component) { var button = new Button { Margin = new Thickness(20, 5, 5, 5), Name = "Shadow Button " + component.Entity.Name, Opacity = component.Enabled ? 1.0f : 0.3f, CanBeHitByUser = component.Enabled, Content = new TextBlock { Font = Font, TextAlignment = TextAlignment.Right, Text = GetButtonTextOnOffShadow(component), }, }; button.Click += ToggleShadowMap; button.DependencyProperties.Set(LightKey, component); parentButton.DependencyProperties.Set(ShadowButtonKey, button); return button; }