From 6f28ba487cf2901f9361c4efd6d7dd89aea1758f Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Thu, 19 Sep 2024 19:49:51 -0700 Subject: [PATCH 01/20] Moved picking to separate class, still called from the render method --- .../Game/UIEditorGameAdornerService.Events.cs | 4 +- .../Rendering/UI/UIRenderFeature.Picking.cs | 54 +++++++++++++++---- .../Stride.UI/Rendering/UI/UIRenderFeature.cs | 44 +++++---------- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs index 4f6ac5ddc1..60fbd4ac30 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs @@ -273,7 +273,7 @@ private void DoHighlightingAtPosition(ref Vector3 worldPosition) DoHighlightAdorner(elementId); } - private ICollection GetAdornerVisualsAtPosition(ref Vector3 worldPosition) + private ICollection GetAdornerVisualsAtPosition(ref Vector3 worldPosition) { var uiComponent = Controller.GetEntityByName(UIEditorController.AdornerEntityName).Get(); if (Math.Abs(worldPosition.X) > uiComponent.Resolution.X * 0.5f || @@ -286,7 +286,7 @@ private void DoHighlightingAtPosition(ref Vector3 worldPosition) var ray = new Ray(new Vector3(worldPosition.X, worldPosition.Y, uiComponent.Resolution.Z + 1), -Vector3.UnitZ); var worldViewProj = Matrix.Identity; // All the calculation is done in UI space - return UIRenderFeature.GetElementsAtPosition(rootElement, ref ray, ref worldViewProj); + return UIInputPicking.GetElementsAtPosition(rootElement, ref ray, ref worldViewProj); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs index 25482244f7..e9375c660f 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Stride.Core.Diagnostics; using Stride.Core.Mathematics; using Stride.Engine; using Stride.Games; @@ -12,17 +13,52 @@ namespace Stride.Rendering.UI { - public partial class UIRenderFeature + public class UIInputPicking { // object to avoid allocation at each element leave event private readonly HashSet newlySelectedElementParents = new HashSet(); - private readonly List compactedPointerEvents = new List(); + private UIRenderFeature uiRenderFeature; + private InputManager input; + private IGame game; - + public UIInputPicking(UIRenderFeature renderFeature, InputManager input, IGame game) + { + uiRenderFeature = renderFeature; + this.input = input; + this.game = game; + } - partial void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, ref Matrix worldViewProj, GameTime drawTime, ref UIElement elementUnderMouseCursor) + public UIElement Pick(RenderDrawContext context, GameTime drawTime) + { + UIElement elementUnderMouseCursor = null; + + // Prepare content required for Picking and MouseOver events + PickingPrepare(); + + + foreach (var uiElementState in uiRenderFeature.uiElementStates) + { + UIElement loopedElementUnderMouseCursor = null; + + // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) + using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) + { + PickingUpdate(uiElementState.RenderObject, context.CommandList.Viewport, ref uiElementState.WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); + + // only update result element, when this one has a value + if (loopedElementUnderMouseCursor != null) + elementUnderMouseCursor = loopedElementUnderMouseCursor; + } + } + + PickingClear(); + + return elementUnderMouseCursor; + } + + private void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, ref Matrix worldViewProj, GameTime drawTime, ref UIElement elementUnderMouseCursor) { if (renderUIElement.Page?.RootElement == null) @@ -35,13 +71,13 @@ partial void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, r UpdateTouchEvents(ref viewport, ref inverseZViewProj, renderUIElement, drawTime); } - partial void PickingClear() + private void PickingClear() { // clear the list of compacted pointer events of time frame ClearPointerEvents(); } - partial void PickingPrepare() + private void PickingPrepare() { // compact all the pointer events that happened since last frame to avoid performing useless hit tests. CompactPointerEvents(); @@ -90,7 +126,7 @@ private void ClearPointerEvents() /// private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix worldViewProj) { - var graphicsDevice = graphicsDeviceService?.GraphicsDevice; + var graphicsDevice = game?.GraphicsDevice; if (graphicsDevice == null) return new Ray(new Vector3(float.NegativeInfinity), new Vector3(0, 1, 0)); @@ -289,9 +325,7 @@ private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewPro parent = parent.VisualParent; } } - - UIElementUnderMouseCursor = mouseOverElement; - + // update cached values state.LastMouseOverElement = mouseOverElement; state.LastMousePosition = mousePosition; diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index fbe5c6d5ae..e900ba8fd0 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -21,7 +21,8 @@ public partial class UIRenderFeature : RootRenderFeature private UISystem uiSystem; private InputManager input; private IGraphicsDeviceService graphicsDeviceService; - + private UIInputPicking picking; + private RendererManager rendererManager; private readonly UIRenderingContext renderingContext = new UIRenderingContext(); @@ -32,7 +33,7 @@ public partial class UIRenderFeature : RootRenderFeature private readonly LayoutingContext layoutingContext = new LayoutingContext(); - private readonly List uiElementStates = new List(); + public readonly List uiElementStates = new List(); public override Type SupportedRenderObjectType => typeof(RenderUIElement); @@ -64,6 +65,8 @@ protected override void InitializeCore() uiSystem = new UISystem(RenderSystem.Services); RenderSystem.Services.AddService(uiSystem); gameSytems.Add(uiSystem); + + picking = new UIInputPicking(this, input, game); } rendererManager = new RendererManager(new DefaultRenderersFactory(RenderSystem.Services)); @@ -71,12 +74,6 @@ protected override void InitializeCore() batch = uiSystem.Batch; } - partial void PickingPrepare(); - - partial void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, ref Matrix worldViewProj, GameTime drawTime, ref UIElement elementUnderMouseCursor); - - partial void PickingClear(); - public override void Draw(RenderDrawContext context, RenderView renderView, RenderViewStage renderViewStage, int startIndex, int endIndex) { lock (drawLock) @@ -115,9 +112,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend renderingContext.GraphicsContext = context.GraphicsContext; renderingContext.Time = drawTime; renderingContext.RenderTarget = context.CommandList.RenderTargets[0]; // TODO: avoid hardcoded index 0 - - // Prepare content required for Picking and MouseOver events - PickingPrepare(); + // allocate temporary graphics resources if needed Texture scopedDepthBuffer = null; @@ -132,10 +127,6 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend } } - // see UIElementUnderMouseCursor property - UIElement elementUnderMouseCursor = null; - - // update view parameters and perform UI picking foreach (var uiElementState in uiElementStates) { @@ -143,8 +134,6 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend var rootElement = renderObject.Page?.RootElement; if (rootElement == null) continue; - - UIElement loopedElementUnderMouseCursor = null; // calculate the size of the virtual resolution depending on target size (UI canvas) var virtualResolution = renderObject.Resolution; @@ -168,19 +157,11 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend if (cameraComponent != null) uiElementState.Update(renderObject, cameraComponent); } - - - // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) - using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) - { - PickingUpdate(uiElementState.RenderObject, context.CommandList.Viewport, ref uiElementState.WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); - - // only update result element, when this one has a value - if (loopedElementUnderMouseCursor != null) - elementUnderMouseCursor = loopedElementUnderMouseCursor; - } } - UIElementUnderMouseCursor = elementUnderMouseCursor; + + // Handle input. + UIElementUnderMouseCursor = picking.Pick(context, drawTime); + // render the UI elements of all the entities foreach (var uiElementState in uiElementStates) @@ -271,8 +252,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend // end the image draw session batch.End(); } - - PickingClear(); + // revert the depth stencil buffer to the default value context.CommandList.SetRenderTargets(context.CommandList.DepthStencilBuffer, context.CommandList.RenderTargetCount, context.CommandList.RenderTargets); @@ -350,7 +330,7 @@ public void RegisterRenderer(UIElement element, ElementRenderer renderer) rendererManager.RegisterRenderer(element, renderer); } - private class UIElementState + public class UIElementState { public readonly RenderUIElement RenderObject; public Matrix WorldViewProjectionMatrix; From e11024920169bda098ace2498c75c159a7ea48a9 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Fri, 20 Sep 2024 05:48:06 -0700 Subject: [PATCH 02/20] Added separate input state for RenderUIElements & changed renderContex to GetShared --- .../Rendering/UI/UIRenderFeature.Picking.cs | 164 +++++++++++++++++- .../Stride.UI/Rendering/UI/UIRenderFeature.cs | 6 +- 2 files changed, 164 insertions(+), 6 deletions(-) diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs index e9375c660f..e4972f79f8 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs @@ -18,6 +18,12 @@ public class UIInputPicking // object to avoid allocation at each element leave event private readonly HashSet newlySelectedElementParents = new HashSet(); private readonly List compactedPointerEvents = new List(); + private readonly Dictionary inputStates = new Dictionary(); + private readonly List oldRenderObjects = new List(); + + private RenderContext renderContext; + + // Temp... private UIRenderFeature uiRenderFeature; private InputManager input; private IGame game; @@ -27,25 +33,32 @@ public UIInputPicking(UIRenderFeature renderFeature, InputManager input, IGame g uiRenderFeature = renderFeature; this.input = input; this.game = game; + + renderContext = RenderContext.GetShared(game.Services); } - + public UIElement Pick(RenderDrawContext context, GameTime drawTime) { + UpdateStates(context); + UIElement elementUnderMouseCursor = null; // Prepare content required for Picking and MouseOver events PickingPrepare(); - foreach (var uiElementState in uiRenderFeature.uiElementStates) + foreach (var renderObject in uiRenderFeature.RenderObjects) { + if (renderObject is not RenderUIElement renderUIElement) + continue; + UIElement loopedElementUnderMouseCursor = null; // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) { - PickingUpdate(uiElementState.RenderObject, context.CommandList.Viewport, ref uiElementState.WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); + PickingUpdate(renderUIElement, context.CommandList.Viewport, ref inputStates[renderUIElement].WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); // only update result element, when this one has a value if (loopedElementUnderMouseCursor != null) @@ -57,6 +70,55 @@ public UIElement Pick(RenderDrawContext context, GameTime drawTime) return elementUnderMouseCursor; } + + private void UpdateStates(RenderDrawContext context) + { + var renderTarget = context.CommandList.RenderTargets[0]; + + oldRenderObjects.AddRange(inputStates.Keys); + foreach (RenderObject renderObject in uiRenderFeature.RenderObjects) + { + if (renderObject is not RenderUIElement renderUIElement) + continue; + + // Create a state for the render object if it doesn't have one already. + if (!inputStates.TryGetValue(renderUIElement, out var state)) + inputStates[renderUIElement] = state = new UIElementInputState(renderUIElement); + + + // calculate the size of the virtual resolution depending on target size (UI canvas) + var virtualResolution = renderUIElement.Resolution; + + if (renderUIElement.IsFullScreen) + { + //var targetSize = viewportSize; + var targetSize = new Vector2(renderTarget.Width, renderTarget.Height); + + // update the virtual resolution of the renderer + if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) + virtualResolution.Y = virtualResolution.X * targetSize.Y / targetSize.X; + if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) + virtualResolution.X = virtualResolution.Y * targetSize.X / targetSize.Y; + + state.Update(renderUIElement, virtualResolution); + } + else + { + var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); + if (cameraComponent != null) + state.Update(renderUIElement, cameraComponent); + } + + // Remove the current render object, so only render objects that have state but have been removed from the render feature are left. + oldRenderObjects.Remove(renderUIElement); + } + + // Remove the state for all remaining render objects, because they are no longer on the render feature. + foreach (RenderUIElement unhandledElement in oldRenderObjects) + { + inputStates.Remove(unhandledElement); + } + } private void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, ref Matrix worldViewProj, GameTime drawTime, ref UIElement elementUnderMouseCursor) @@ -510,4 +572,100 @@ public HitTestResult(float depthBias, UIElement element, Vector3 intersection) public Vector3 IntersectionPoint { get; } } } + + public class UIElementInputState + { + public readonly RenderUIElement RenderObject; + public Matrix WorldViewProjectionMatrix; + + public UIElementInputState(RenderUIElement renderObject) + { + RenderObject = renderObject; + WorldViewProjectionMatrix = Matrix.Identity; + } + + public void Update(RenderUIElement renderObject, CameraComponent camera) + { + var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); + + var worldMatrix = renderObject.WorldMatrix; + + // rotate the UI element perpendicular to the camera view vector, if billboard is activated + if (renderObject.IsFullScreen) + { + worldMatrix = Matrix.Identity; + } + else + { + Matrix viewInverse; + Matrix.Invert(ref camera.ViewMatrix, out viewInverse); + var forwardVector = viewInverse.Forward; + + if (renderObject.IsBillboard) + { + var viewInverseRow1 = viewInverse.Row1; + var viewInverseRow2 = viewInverse.Row2; + + // remove scale of the camera + viewInverseRow1 /= viewInverseRow1.XYZ().Length(); + viewInverseRow2 /= viewInverseRow2.XYZ().Length(); + + // set the scale of the object + viewInverseRow1 *= worldMatrix.Row1.XYZ().Length(); + viewInverseRow2 *= worldMatrix.Row2.XYZ().Length(); + + // set the adjusted world matrix + worldMatrix.Row1 = viewInverseRow1; + worldMatrix.Row2 = viewInverseRow2; + worldMatrix.Row3 = viewInverse.Row3; + } + + if (renderObject.IsFixedSize) + { + forwardVector.Normalize(); + var distVec = (worldMatrix.TranslationVector - viewInverse.TranslationVector); + float distScalar; + Vector3.Dot(ref forwardVector, ref distVec, out distScalar); + distScalar = Math.Abs(distScalar); + + var worldScale = frustumHeight * distScalar * UIComponent.FixedSizeVerticalUnit; // FrustumHeight already is 2*Tan(FOV/2) + + worldMatrix.Row1 *= worldScale; + worldMatrix.Row2 *= worldScale; + worldMatrix.Row3 *= worldScale; + } + + // If the UI component is not drawn fullscreen it should be drawn as a quad with world sizes corresponding to its actual size + worldMatrix = Matrix.Scaling(renderObject.Size / renderObject.Resolution) * worldMatrix; + } + + // Rotation of Pi along 0x to go from UI space to world space + worldMatrix.Row2 = -worldMatrix.Row2; + worldMatrix.Row3 = -worldMatrix.Row3; + + Matrix worldViewMatrix; + Matrix.Multiply(ref worldMatrix, ref camera.ViewMatrix, out worldViewMatrix); + Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out WorldViewProjectionMatrix); + } + + public void Update(RenderUIElement renderObject, Vector3 virtualResolution) + { + var nearPlane = virtualResolution.Z / 2; + var farPlane = nearPlane + virtualResolution.Z; + var zOffset = nearPlane + virtualResolution.Z / 2; + var aspectRatio = virtualResolution.X / virtualResolution.Y; + var verticalFov = MathF.Atan2(virtualResolution.Y / 2, zOffset) * 2; + + var cameraComponent = new CameraComponent(nearPlane, farPlane) + { + UseCustomAspectRatio = true, + AspectRatio = aspectRatio, + VerticalFieldOfView = MathUtil.RadiansToDegrees(verticalFov), + ViewMatrix = Matrix.LookAtRH(new Vector3(0, 0, zOffset), Vector3.Zero, Vector3.UnitY), + ProjectionMatrix = Matrix.PerspectiveFovRH(verticalFov, aspectRatio, nearPlane, farPlane), + }; + + Update(renderObject, cameraComponent); + } + } } diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index e900ba8fd0..726a914447 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -33,7 +33,7 @@ public partial class UIRenderFeature : RootRenderFeature private readonly LayoutingContext layoutingContext = new LayoutingContext(); - public readonly List uiElementStates = new List(); + private readonly List uiElementStates = new List(); public override Type SupportedRenderObjectType => typeof(RenderUIElement); @@ -160,7 +160,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend } // Handle input. - UIElementUnderMouseCursor = picking.Pick(context, drawTime); + UIElementUnderMouseCursor = picking?.Pick(context, drawTime); // render the UI elements of all the entities @@ -330,7 +330,7 @@ public void RegisterRenderer(UIElement element, ElementRenderer renderer) rendererManager.RegisterRenderer(element, renderer); } - public class UIElementState + private class UIElementState { public readonly RenderUIElement RenderObject; public Matrix WorldViewProjectionMatrix; From 5c4d6281c5531f6ac12ede253a68f5a0bba14192 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Fri, 20 Sep 2024 06:16:43 -0700 Subject: [PATCH 03/20] Removed requirement for RenderDrawContext from UI picking --- .../Rendering/UI/UIRenderFeature.Picking.cs | 12 +++++++----- .../engine/Stride.UI/Rendering/UI/UIRenderFeature.cs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs index e4972f79f8..7507c8f08d 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs @@ -38,15 +38,17 @@ public UIInputPicking(UIRenderFeature renderFeature, InputManager input, IGame g } - public UIElement Pick(RenderDrawContext context, GameTime drawTime) + public UIElement Pick( GameTime drawTime) { - UpdateStates(context); + UpdateStates(); UIElement elementUnderMouseCursor = null; // Prepare content required for Picking and MouseOver events PickingPrepare(); + // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). + var viewport = renderContext.ViewportState.Viewport0; foreach (var renderObject in uiRenderFeature.RenderObjects) { @@ -58,7 +60,7 @@ public UIElement Pick(RenderDrawContext context, GameTime drawTime) // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) { - PickingUpdate(renderUIElement, context.CommandList.Viewport, ref inputStates[renderUIElement].WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); + PickingUpdate(renderUIElement, viewport, ref inputStates[renderUIElement].WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); // only update result element, when this one has a value if (loopedElementUnderMouseCursor != null) @@ -71,9 +73,9 @@ public UIElement Pick(RenderDrawContext context, GameTime drawTime) return elementUnderMouseCursor; } - private void UpdateStates(RenderDrawContext context) + private void UpdateStates() { - var renderTarget = context.CommandList.RenderTargets[0]; + var renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; oldRenderObjects.AddRange(inputStates.Keys); foreach (RenderObject renderObject in uiRenderFeature.RenderObjects) diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index 726a914447..5494f4ad77 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -160,7 +160,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend } // Handle input. - UIElementUnderMouseCursor = picking?.Pick(context, drawTime); + UIElementUnderMouseCursor = picking?.Pick(drawTime); // render the UI elements of all the entities From 3c8c2d21e14a4af13b33a0d9872785a38a904a93 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Fri, 20 Sep 2024 06:18:12 -0700 Subject: [PATCH 04/20] Renamed UIInputPicking to UIInputSystem --- .../UIEditor/Game/UIEditorGameAdornerService.Events.cs | 4 ++-- sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs | 6 +++--- .../UI/UIRenderFeature.Picking.cs => UIInputSystem.cs} | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) rename sources/engine/Stride.UI/{Rendering/UI/UIRenderFeature.Picking.cs => UIInputSystem.cs} (99%) diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs index 60fbd4ac30..a3cd4055d7 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs @@ -273,7 +273,7 @@ private void DoHighlightingAtPosition(ref Vector3 worldPosition) DoHighlightAdorner(elementId); } - private ICollection GetAdornerVisualsAtPosition(ref Vector3 worldPosition) + private ICollection GetAdornerVisualsAtPosition(ref Vector3 worldPosition) { var uiComponent = Controller.GetEntityByName(UIEditorController.AdornerEntityName).Get(); if (Math.Abs(worldPosition.X) > uiComponent.Resolution.X * 0.5f || @@ -286,7 +286,7 @@ private void DoHighlightingAtPosition(ref Vector3 worldPosition) var ray = new Ray(new Vector3(worldPosition.X, worldPosition.Y, uiComponent.Resolution.Z + 1), -Vector3.UnitZ); var worldViewProj = Matrix.Identity; // All the calculation is done in UI space - return UIInputPicking.GetElementsAtPosition(rootElement, ref ray, ref worldViewProj); + return UIInputSystem.GetElementsAtPosition(rootElement, ref ray, ref worldViewProj); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index 5494f4ad77..6baeb53ae7 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -21,7 +21,7 @@ public partial class UIRenderFeature : RootRenderFeature private UISystem uiSystem; private InputManager input; private IGraphicsDeviceService graphicsDeviceService; - private UIInputPicking picking; + private UIInputSystem uiInput; private RendererManager rendererManager; @@ -66,7 +66,7 @@ protected override void InitializeCore() RenderSystem.Services.AddService(uiSystem); gameSytems.Add(uiSystem); - picking = new UIInputPicking(this, input, game); + uiInput = new UIInputSystem(this, input, game); } rendererManager = new RendererManager(new DefaultRenderersFactory(RenderSystem.Services)); @@ -160,7 +160,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend } // Handle input. - UIElementUnderMouseCursor = picking?.Pick(drawTime); + UIElementUnderMouseCursor = uiInput?.Pick(drawTime); // render the UI elements of all the entities diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs b/sources/engine/Stride.UI/UIInputSystem.cs similarity index 99% rename from sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs rename to sources/engine/Stride.UI/UIInputSystem.cs index 7507c8f08d..bdc7bdfcb1 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.Picking.cs +++ b/sources/engine/Stride.UI/UIInputSystem.cs @@ -9,11 +9,12 @@ using Stride.Games; using Stride.Graphics; using Stride.Input; -using Stride.UI; +using Stride.Rendering; +using Stride.Rendering.UI; -namespace Stride.Rendering.UI +namespace Stride.UI { - public class UIInputPicking + public class UIInputSystem { // object to avoid allocation at each element leave event private readonly HashSet newlySelectedElementParents = new HashSet(); @@ -28,7 +29,7 @@ public class UIInputPicking private InputManager input; private IGame game; - public UIInputPicking(UIRenderFeature renderFeature, InputManager input, IGame game) + public UIInputSystem(UIRenderFeature renderFeature, InputManager input, IGame game) { uiRenderFeature = renderFeature; this.input = input; From 4e80cce592d678141fb69bb71f6000b2aeccfff2 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Fri, 20 Sep 2024 14:06:04 -0700 Subject: [PATCH 05/20] Changed UIInputPicking to part of UISystem and got picking working --- .../Game/UIEditorGameAdornerService.Events.cs | 4 +- .../Stride.UI/Rendering/UI/UIRenderFeature.cs | 29 +++---- .../{UIInputSystem.cs => UISystem.Picking.cs} | 87 +++++++++---------- sources/engine/Stride.UI/UISystem.cs | 22 ++++- 4 files changed, 76 insertions(+), 66 deletions(-) rename sources/engine/Stride.UI/{UIInputSystem.cs => UISystem.Picking.cs} (92%) diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs index a3cd4055d7..d3dcf43796 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs @@ -273,7 +273,7 @@ private void DoHighlightingAtPosition(ref Vector3 worldPosition) DoHighlightAdorner(elementId); } - private ICollection GetAdornerVisualsAtPosition(ref Vector3 worldPosition) + private ICollection GetAdornerVisualsAtPosition(ref Vector3 worldPosition) { var uiComponent = Controller.GetEntityByName(UIEditorController.AdornerEntityName).Get(); if (Math.Abs(worldPosition.X) > uiComponent.Resolution.X * 0.5f || @@ -286,7 +286,7 @@ private void DoHighlightingAtPosition(ref Vector3 worldPosition) var ray = new Ray(new Vector3(worldPosition.X, worldPosition.Y, uiComponent.Resolution.Z + 1), -Vector3.UnitZ); var worldViewProj = Matrix.Identity; // All the calculation is done in UI space - return UIInputSystem.GetElementsAtPosition(rootElement, ref ray, ref worldViewProj); + return UISystem.GetElementsAtPosition(rootElement, ref ray, ref worldViewProj); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index 6baeb53ae7..14535ac8d3 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; @@ -19,9 +20,6 @@ public partial class UIRenderFeature : RootRenderFeature { private IGame game; private UISystem uiSystem; - private InputManager input; - private IGraphicsDeviceService graphicsDeviceService; - private UIInputSystem uiInput; private RendererManager rendererManager; @@ -36,13 +34,7 @@ public partial class UIRenderFeature : RootRenderFeature private readonly List uiElementStates = new List(); public override Type SupportedRenderObjectType => typeof(RenderUIElement); - - /// - /// Represents the UI-element thats currently under the mouse cursor. - /// Only elements with CanBeHitByUser == true are taken into account. - /// Last processed element_state / ?UIComponent? with a valid element will be used. - /// - public UIElement UIElementUnderMouseCursor { get; private set; } + public UIRenderFeature() { @@ -55,18 +47,15 @@ protected override void InitializeCore() Name = "UIComponentRenderer"; game = RenderSystem.Services.GetService(); - input = RenderSystem.Services.GetService(); uiSystem = RenderSystem.Services.GetService(); - graphicsDeviceService = RenderSystem.Services.GetSafeServiceAs(); if (uiSystem == null) { - var gameSytems = RenderSystem.Services.GetSafeServiceAs(); + var gameSystems = RenderSystem.Services.GetSafeServiceAs(); + uiSystem = new UISystem(RenderSystem.Services); RenderSystem.Services.AddService(uiSystem); - gameSytems.Add(uiSystem); - - uiInput = new UIInputSystem(this, input, game); + gameSystems.Add(uiSystem); } rendererManager = new RendererManager(new DefaultRenderersFactory(RenderSystem.Services)); @@ -160,7 +149,13 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend } // Handle input. - UIElementUnderMouseCursor = uiInput?.Pick(drawTime); + //UIElementUnderMouseCursor = uiInput?.Pick(drawTime); + + uiSystem.RenderObjects.Clear(); + uiSystem.RenderObjects.UnionWith(RenderObjects.OfType()); + + var cam = context.RenderContext.Tags.Get(CameraComponentRendererExtensions.Current); + uiSystem.Cameras.Add(cam); // render the UI elements of all the entities diff --git a/sources/engine/Stride.UI/UIInputSystem.cs b/sources/engine/Stride.UI/UISystem.Picking.cs similarity index 92% rename from sources/engine/Stride.UI/UIInputSystem.cs rename to sources/engine/Stride.UI/UISystem.Picking.cs index bdc7bdfcb1..6e7e6b99a5 100644 --- a/sources/engine/Stride.UI/UIInputSystem.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; using Stride.Engine; @@ -14,7 +16,7 @@ namespace Stride.UI { - public class UIInputSystem + public partial class UISystem { // object to avoid allocation at each element leave event private readonly HashSet newlySelectedElementParents = new HashSet(); @@ -22,67 +24,65 @@ public class UIInputSystem private readonly Dictionary inputStates = new Dictionary(); private readonly List oldRenderObjects = new List(); - private RenderContext renderContext; + public HashSet RenderObjects { get; } = new HashSet(); + + public HashSet Cameras { get; set; } = new HashSet(); - // Temp... - private UIRenderFeature uiRenderFeature; - private InputManager input; - private IGame game; + /// + /// Represents the UI-element thats currently under the mouse cursor. + /// Only elements with CanBeHitByUser == true are taken into account. + /// Last processed element_state / ?UIComponent? with a valid element will be used. + /// + public UIElement UIElementUnderMouseCursor { get; internal set; } - public UIInputSystem(UIRenderFeature renderFeature, InputManager input, IGame game) + private partial UIElement Pick( GameTime drawTime) { - uiRenderFeature = renderFeature; - this.input = input; - this.game = game; + if (renderContext == null) + return null; - renderContext = RenderContext.GetShared(game.Services); - } - - - public UIElement Pick( GameTime drawTime) - { - UpdateStates(); - UIElement elementUnderMouseCursor = null; - // Prepare content required for Picking and MouseOver events - PickingPrepare(); + foreach (CameraComponent cameraComponent in Cameras) + { + UpdateStates(cameraComponent); + + // Prepare content required for Picking and MouseOver events + PickingPrepare(); - // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). - var viewport = renderContext.ViewportState.Viewport0; + // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). + var viewport = renderContext.ViewportState.Viewport0; - foreach (var renderObject in uiRenderFeature.RenderObjects) - { - if (renderObject is not RenderUIElement renderUIElement) - continue; + foreach (var renderUIElement in RenderObjects) + { - UIElement loopedElementUnderMouseCursor = null; + UIElement loopedElementUnderMouseCursor = null; - // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) - using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) - { - PickingUpdate(renderUIElement, viewport, ref inputStates[renderUIElement].WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); + // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) + using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) + { + PickingUpdate(renderUIElement, viewport, ref inputStates[renderUIElement].WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); - // only update result element, when this one has a value - if (loopedElementUnderMouseCursor != null) - elementUnderMouseCursor = loopedElementUnderMouseCursor; + // only update result element, when this one has a value + if (loopedElementUnderMouseCursor != null) + elementUnderMouseCursor = loopedElementUnderMouseCursor; + } } - } + } + + PickingClear(); return elementUnderMouseCursor; } - private void UpdateStates() + private void UpdateStates(CameraComponent cameraComponent) { var renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; oldRenderObjects.AddRange(inputStates.Keys); - foreach (RenderObject renderObject in uiRenderFeature.RenderObjects) + foreach (RenderUIElement renderUIElement in RenderObjects) { - if (renderObject is not RenderUIElement renderUIElement) - continue; // Create a state for the render object if it doesn't have one already. if (!inputStates.TryGetValue(renderUIElement, out var state)) @@ -107,7 +107,7 @@ private void UpdateStates() } else { - var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); + //var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); if (cameraComponent != null) state.Update(renderUIElement, cameraComponent); } @@ -191,12 +191,11 @@ private void ClearPointerEvents() /// private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix worldViewProj) { - var graphicsDevice = game?.GraphicsDevice; - if (graphicsDevice == null) + if (GraphicsDevice == null) return new Ray(new Vector3(float.NegativeInfinity), new Vector3(0, 1, 0)); - screenPos.X *= graphicsDevice.Presenter.BackBuffer.Width; - screenPos.Y *= graphicsDevice.Presenter.BackBuffer.Height; + screenPos.X *= GraphicsDevice.Presenter.BackBuffer.Width; + screenPos.Y *= GraphicsDevice.Presenter.BackBuffer.Height; var unprojectedNear = viewport.Unproject(new Vector3(screenPos, 0.0f), ref worldViewProj); diff --git a/sources/engine/Stride.UI/UISystem.cs b/sources/engine/Stride.UI/UISystem.cs index 6e09cbd4a7..a4d373abb0 100644 --- a/sources/engine/Stride.UI/UISystem.cs +++ b/sources/engine/Stride.UI/UISystem.cs @@ -2,11 +2,16 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System; +using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using Stride.Core; +using Stride.Core.Collections; using Stride.Games; using Stride.Graphics; using Stride.Input; +using Stride.Rendering; +using Stride.Rendering.UI; using Stride.UI.Controls; namespace Stride.UI @@ -14,7 +19,7 @@ namespace Stride.UI /// /// Interface of the UI system. /// - public class UISystem : GameSystemBase + public partial class UISystem : GameSystemBase { internal UIBatch Batch { get; private set; } @@ -25,10 +30,12 @@ public class UISystem : GameSystemBase internal DepthStencilStateDescription DecreaseStencilValueState { get; private set; } private InputManager input; - + private RenderContext renderContext; + public UISystem(IServiceRegistry registry) : base(registry) { + } public override void Initialize() @@ -119,10 +126,19 @@ private static void OnApplicationResumed(object sender, EventArgs e) public override void Update(GameTime gameTime) { base.Update(gameTime); + + if (renderContext == null) + { + renderContext = RenderContext.GetShared(Services); + } + + UIElementUnderMouseCursor = Pick(gameTime); UpdateKeyEvents(); } + private partial UIElement Pick(GameTime drawTime); + private void UpdateKeyEvents() { if (input == null) @@ -167,4 +183,4 @@ private void UpdateKeyEvents() } } } -} \ No newline at end of file +} From 02dc53873edbf18474652e1fc748bf6763e2d1ef Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Fri, 20 Sep 2024 14:49:44 -0700 Subject: [PATCH 06/20] Changed UISystem to use SceneSystem to get cameras --- .../engine/Stride.UI/Rendering/UI/UIRenderFeature.cs | 3 --- sources/engine/Stride.UI/UISystem.Picking.cs | 10 +++------- sources/engine/Stride.UI/UISystem.cs | 3 +++ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index 14535ac8d3..8ae55553fc 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -153,9 +153,6 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend uiSystem.RenderObjects.Clear(); uiSystem.RenderObjects.UnionWith(RenderObjects.OfType()); - - var cam = context.RenderContext.Tags.Get(CameraComponentRendererExtensions.Current); - uiSystem.Cameras.Add(cam); // render the UI elements of all the entities diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index 6e7e6b99a5..9ffcedc8e0 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -25,8 +25,6 @@ public partial class UISystem private readonly List oldRenderObjects = new List(); public HashSet RenderObjects { get; } = new HashSet(); - - public HashSet Cameras { get; set; } = new HashSet(); /// /// Represents the UI-element thats currently under the mouse cursor. @@ -37,14 +35,14 @@ public partial class UISystem private partial UIElement Pick( GameTime drawTime) { - if (renderContext == null) + if (renderContext == null || sceneSystem == null || sceneSystem.GraphicsCompositor == null) return null; UIElement elementUnderMouseCursor = null; - foreach (CameraComponent cameraComponent in Cameras) + foreach (var cameraSlot in sceneSystem.GraphicsCompositor.Cameras) { - UpdateStates(cameraComponent); + UpdateStates(cameraSlot.Camera); // Prepare content required for Picking and MouseOver events PickingPrepare(); @@ -67,10 +65,8 @@ private partial UIElement Pick( GameTime drawTime) elementUnderMouseCursor = loopedElementUnderMouseCursor; } } - } - PickingClear(); return elementUnderMouseCursor; diff --git a/sources/engine/Stride.UI/UISystem.cs b/sources/engine/Stride.UI/UISystem.cs index a4d373abb0..5439dbfca0 100644 --- a/sources/engine/Stride.UI/UISystem.cs +++ b/sources/engine/Stride.UI/UISystem.cs @@ -7,6 +7,7 @@ using System.Linq; using Stride.Core; using Stride.Core.Collections; +using Stride.Engine; using Stride.Games; using Stride.Graphics; using Stride.Input; @@ -31,6 +32,7 @@ public partial class UISystem : GameSystemBase private InputManager input; private RenderContext renderContext; + private SceneSystem sceneSystem; public UISystem(IServiceRegistry registry) : base(registry) @@ -43,6 +45,7 @@ public override void Initialize() base.Initialize(); input = Services.GetService(); + sceneSystem = Services.GetService(); Enabled = true; Visible = false; From 4c1659c6eefa78897abcc00b241edf2b72ffe4c0 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Sat, 28 Sep 2024 05:17:34 -0700 Subject: [PATCH 07/20] Moved GetWorldViewProjectMatix to standalone method in UISystem --- sources/engine/Stride.UI/UISystem.Picking.cs | 142 ++++++++++--------- sources/engine/Stride.UI/UISystem.cs | 7 +- 2 files changed, 75 insertions(+), 74 deletions(-) diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index 9ffcedc8e0..7b5f84bc58 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -33,16 +33,17 @@ public partial class UISystem /// public UIElement UIElementUnderMouseCursor { get; internal set; } - private partial UIElement Pick( GameTime drawTime) + private partial UIElement Pick(GameTime gameTime) { if (renderContext == null || sceneSystem == null || sceneSystem.GraphicsCompositor == null) return null; + Texture renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; UIElement elementUnderMouseCursor = null; foreach (var cameraSlot in sceneSystem.GraphicsCompositor.Cameras) { - UpdateStates(cameraSlot.Camera); + UpdateStates(); // Prepare content required for Picking and MouseOver events PickingPrepare(); @@ -52,13 +53,13 @@ private partial UIElement Pick( GameTime drawTime) foreach (var renderUIElement in RenderObjects) { - UIElement loopedElementUnderMouseCursor = null; - + + Matrix worldViewProjection = GetWorldViewProjection(renderUIElement, cameraSlot.Camera, renderTarget); // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) { - PickingUpdate(renderUIElement, viewport, ref inputStates[renderUIElement].WorldViewProjectionMatrix, drawTime, ref loopedElementUnderMouseCursor); + PickingUpdate(renderUIElement, viewport, ref worldViewProjection, gameTime, ref loopedElementUnderMouseCursor); // only update result element, when this one has a value if (loopedElementUnderMouseCursor != null) @@ -72,41 +73,15 @@ private partial UIElement Pick( GameTime drawTime) return elementUnderMouseCursor; } - private void UpdateStates(CameraComponent cameraComponent) + private void UpdateStates() { - var renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; - oldRenderObjects.AddRange(inputStates.Keys); foreach (RenderUIElement renderUIElement in RenderObjects) { // Create a state for the render object if it doesn't have one already. if (!inputStates.TryGetValue(renderUIElement, out var state)) - inputStates[renderUIElement] = state = new UIElementInputState(renderUIElement); - - - // calculate the size of the virtual resolution depending on target size (UI canvas) - var virtualResolution = renderUIElement.Resolution; - - if (renderUIElement.IsFullScreen) - { - //var targetSize = viewportSize; - var targetSize = new Vector2(renderTarget.Width, renderTarget.Height); - - // update the virtual resolution of the renderer - if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) - virtualResolution.Y = virtualResolution.X * targetSize.Y / targetSize.X; - if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) - virtualResolution.X = virtualResolution.Y * targetSize.X / targetSize.Y; - - state.Update(renderUIElement, virtualResolution); - } - else - { - //var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); - if (cameraComponent != null) - state.Update(renderUIElement, cameraComponent); - } + inputStates[renderUIElement] = state = new UIElementInputState(); // Remove the current render object, so only render objects that have state but have been removed from the render feature are left. oldRenderObjects.Remove(renderUIElement); @@ -545,44 +520,37 @@ private static void PerformRecursiveHitTest(UIElement element, ref Ray ray, ref PerformRecursiveHitTest(child, ref ray, ref worldViewProj, results); } - /// - /// Represents the result of a hit test on the UI. - /// - public class HitTestResult + public static Matrix GetWorldViewProjection(RenderUIElement renderUIElement, CameraComponent camera, Texture renderTarget) { - public HitTestResult(float depthBias, UIElement element, Vector3 intersection) - { - DepthBias = depthBias; - Element = element; - IntersectionPoint = intersection; - } - - public float DepthBias { get; } + Matrix worldViewProjection = Matrix.Identity; + + // calculate the size of the virtual resolution depending on target size (UI canvas) + var virtualResolution = renderUIElement.Resolution; - /// - /// Element that was hit. - /// - public UIElement Element { get; } + if (renderUIElement.IsFullScreen) + { + //var targetSize = viewportSize; + var targetSize = new Vector2(renderTarget.Width, renderTarget.Height); - /// - /// Point of intersection between the ray and the hit element. - /// - public Vector3 IntersectionPoint { get; } - } - } + // update the virtual resolution of the renderer + if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) + virtualResolution.Y = virtualResolution.X * targetSize.Y / targetSize.X; + if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) + virtualResolution.X = virtualResolution.Y * targetSize.X / targetSize.Y; - public class UIElementInputState - { - public readonly RenderUIElement RenderObject; - public Matrix WorldViewProjectionMatrix; + worldViewProjection = GetWorldViewProjection(renderUIElement, virtualResolution); + } + else + { + //var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); + if (camera != null) + worldViewProjection = GetWorldViewProjection(renderUIElement, camera); + } - public UIElementInputState(RenderUIElement renderObject) - { - RenderObject = renderObject; - WorldViewProjectionMatrix = Matrix.Identity; + return worldViewProjection; } - - public void Update(RenderUIElement renderObject, CameraComponent camera) + + private static Matrix GetWorldViewProjection(RenderUIElement renderObject, CameraComponent camera) { var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); @@ -642,11 +610,14 @@ public void Update(RenderUIElement renderObject, CameraComponent camera) worldMatrix.Row3 = -worldMatrix.Row3; Matrix worldViewMatrix; + Matrix worldViewProjectionMatrix; Matrix.Multiply(ref worldMatrix, ref camera.ViewMatrix, out worldViewMatrix); - Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out WorldViewProjectionMatrix); + Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out worldViewProjectionMatrix); + + return worldViewProjectionMatrix; } - public void Update(RenderUIElement renderObject, Vector3 virtualResolution) + private static Matrix GetWorldViewProjection(RenderUIElement renderObject, Vector3 virtualResolution) { var nearPlane = virtualResolution.Z / 2; var farPlane = nearPlane + virtualResolution.Z; @@ -663,7 +634,42 @@ public void Update(RenderUIElement renderObject, Vector3 virtualResolution) ProjectionMatrix = Matrix.PerspectiveFovRH(verticalFov, aspectRatio, nearPlane, farPlane), }; - Update(renderObject, cameraComponent); + return GetWorldViewProjection(renderObject, cameraComponent); + } + + /// + /// Represents the result of a hit test on the UI. + /// + public class HitTestResult + { + public HitTestResult(float depthBias, UIElement element, Vector3 intersection) + { + DepthBias = depthBias; + Element = element; + IntersectionPoint = intersection; + } + + public float DepthBias { get; } + + /// + /// Element that was hit. + /// + public UIElement Element { get; } + + /// + /// Point of intersection between the ray and the hit element. + /// + public Vector3 IntersectionPoint { get; } + } + } + + public class UIElementInputState + { + + + public UIElementInputState() + { + } } } diff --git a/sources/engine/Stride.UI/UISystem.cs b/sources/engine/Stride.UI/UISystem.cs index 5439dbfca0..b0ba8a825e 100644 --- a/sources/engine/Stride.UI/UISystem.cs +++ b/sources/engine/Stride.UI/UISystem.cs @@ -3,16 +3,13 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using Stride.Core; -using Stride.Core.Collections; using Stride.Engine; using Stride.Games; using Stride.Graphics; using Stride.Input; using Stride.Rendering; -using Stride.Rendering.UI; using Stride.UI.Controls; namespace Stride.UI @@ -131,16 +128,14 @@ public override void Update(GameTime gameTime) base.Update(gameTime); if (renderContext == null) - { renderContext = RenderContext.GetShared(Services); - } UIElementUnderMouseCursor = Pick(gameTime); UpdateKeyEvents(); } - private partial UIElement Pick(GameTime drawTime); + private partial UIElement Pick(GameTime gameTime); private void UpdateKeyEvents() { From 55d01274ac3f4848db7990a636324fb989da09a1 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Sun, 29 Sep 2024 06:57:42 -0700 Subject: [PATCH 08/20] Minor refactor of UISystem and renamed RenderUIElement to RenderUIPage --- .../{RenderUIElement.cs => RenderUIPage.cs} | 6 +- .../Stride.UI/Rendering/UI/UIRenderFeature.cs | 15 +- .../Rendering/UI/UIRenderProcessor.cs | 20 +- sources/engine/Stride.UI/UISystem.Picking.cs | 194 +++++++++--------- sources/engine/Stride.UI/UISystem.cs | 4 +- 5 files changed, 116 insertions(+), 123 deletions(-) rename sources/engine/Stride.UI/Rendering/UI/{RenderUIElement.cs => RenderUIPage.cs} (92%) diff --git a/sources/engine/Stride.UI/Rendering/UI/RenderUIElement.cs b/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs similarity index 92% rename from sources/engine/Stride.UI/Rendering/UI/RenderUIElement.cs rename to sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs index 982b07aef7..69fd2a6b34 100644 --- a/sources/engine/Stride.UI/Rendering/UI/RenderUIElement.cs +++ b/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs @@ -19,9 +19,9 @@ public enum UIElementSampler AnisotropicClamp, } - public class RenderUIElement : RenderObject + public class RenderUIPage : RenderObject { - public RenderUIElement() + public RenderUIPage() { } @@ -46,7 +46,7 @@ public RenderUIElement() /// /// Last element over which the mouse cursor was registered /// - public UIElement LastMouseOverElement; + public UIElement LastPointerOverElement; /// /// Last element which received a touch/click event diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index 8ae55553fc..cf89029f66 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -33,7 +33,7 @@ public partial class UIRenderFeature : RootRenderFeature private readonly List uiElementStates = new List(); - public override Type SupportedRenderObjectType => typeof(RenderUIElement); + public override Type SupportedRenderObjectType => typeof(RenderUIPage); public UIRenderFeature() @@ -89,7 +89,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend { var renderNodeReference = renderViewStage.SortedRenderNodes[index].RenderNode; var renderNode = GetRenderNode(renderNodeReference); - var renderElement = (RenderUIElement)renderNode.RenderObject; + var renderElement = (RenderUIPage)renderNode.RenderObject; uiElementStates.Add(new UIElementState(renderElement)); } @@ -151,8 +151,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend // Handle input. //UIElementUnderMouseCursor = uiInput?.Pick(drawTime); - uiSystem.RenderObjects.Clear(); - uiSystem.RenderObjects.UnionWith(RenderObjects.OfType()); + uiSystem.RenderObjects.UnionWith(RenderObjects.OfType()); // render the UI elements of all the entities @@ -324,16 +323,16 @@ public void RegisterRenderer(UIElement element, ElementRenderer renderer) private class UIElementState { - public readonly RenderUIElement RenderObject; + public readonly RenderUIPage RenderObject; public Matrix WorldViewProjectionMatrix; - public UIElementState(RenderUIElement renderObject) + public UIElementState(RenderUIPage renderObject) { RenderObject = renderObject; WorldViewProjectionMatrix = Matrix.Identity; } - public void Update(RenderUIElement renderObject, CameraComponent camera) + public void Update(RenderUIPage renderObject, CameraComponent camera) { var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); @@ -397,7 +396,7 @@ public void Update(RenderUIElement renderObject, CameraComponent camera) Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out WorldViewProjectionMatrix); } - public void Update(RenderUIElement renderObject, Vector3 virtualResolution) + public void Update(RenderUIPage renderObject, Vector3 virtualResolution) { var nearPlane = virtualResolution.Z / 2; var farPlane = nearPlane + virtualResolution.Z; diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs index 5c8393aa14..7d9624b7d3 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs @@ -10,9 +10,9 @@ namespace Stride.Rendering.UI /// /// The processor in charge of updating and drawing the entities having UI components. /// - public class UIRenderProcessor : EntityProcessor, IEntityComponentRenderProcessor + public class UIRenderProcessor : EntityProcessor, IEntityComponentRenderProcessor { - public List UIRoots { get; private set; } + public List UIRoots { get; private set; } public VisibilityGroup VisibilityGroup { get; set; } @@ -22,7 +22,7 @@ public class UIRenderProcessor : EntityProcessor, public UIRenderProcessor() : base(typeof(TransformComponent)) { - UIRoots = new List(); + UIRoots = new List(); } public override void Draw(RenderContext gameTime) @@ -59,22 +59,22 @@ public override void Draw(RenderContext gameTime) } } - protected override void OnEntityComponentAdding(Entity entity, UIComponent uiComponent, RenderUIElement renderUIElement) + protected override void OnEntityComponentAdding(Entity entity, UIComponent uiComponent, RenderUIPage renderUIPage) { - VisibilityGroup.RenderObjects.Add(renderUIElement); + VisibilityGroup.RenderObjects.Add(renderUIPage); } - protected override void OnEntityComponentRemoved(Entity entity, UIComponent uiComponent, RenderUIElement renderUIElement) + protected override void OnEntityComponentRemoved(Entity entity, UIComponent uiComponent, RenderUIPage renderUIPage) { - VisibilityGroup.RenderObjects.Remove(renderUIElement); + VisibilityGroup.RenderObjects.Remove(renderUIPage); } - protected override RenderUIElement GenerateComponentData(Entity entity, UIComponent component) + protected override RenderUIPage GenerateComponentData(Entity entity, UIComponent component) { - return new RenderUIElement { Source = component }; + return new RenderUIPage { Source = component }; } - protected override bool IsAssociatedDataValid(Entity entity, UIComponent component, RenderUIElement associatedData) + protected override bool IsAssociatedDataValid(Entity entity, UIComponent component, RenderUIPage associatedData) { return associatedData.Source == component; } diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index 7b5f84bc58..091f2502c1 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -21,96 +21,89 @@ public partial class UISystem // object to avoid allocation at each element leave event private readonly HashSet newlySelectedElementParents = new HashSet(); private readonly List compactedPointerEvents = new List(); - private readonly Dictionary inputStates = new Dictionary(); - private readonly List oldRenderObjects = new List(); + private readonly Dictionary inputStates = new Dictionary(); + private readonly List previousRenderObjects = new List(); - public HashSet RenderObjects { get; } = new HashSet(); + public HashSet RenderObjects { get; } = new HashSet(); /// - /// Represents the UI-element thats currently under the mouse cursor. + /// Represents the UI-element that's currently under the mouse cursor. /// Only elements with CanBeHitByUser == true are taken into account. /// Last processed element_state / ?UIComponent? with a valid element will be used. /// public UIElement UIElementUnderMouseCursor { get; internal set; } - private partial UIElement Pick(GameTime gameTime) + private partial void Pick(GameTime gameTime) { if (renderContext == null || sceneSystem == null || sceneSystem.GraphicsCompositor == null) - return null; + return; Texture renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; - UIElement elementUnderMouseCursor = null; + UIElement elementUnderPointer = null; + + UpdateUIInputStates(); + + // Prepare content required for Picking and MouseOver events + PickingPrepare(); foreach (var cameraSlot in sceneSystem.GraphicsCompositor.Cameras) { - UpdateStates(); - - // Prepare content required for Picking and MouseOver events - PickingPrepare(); - // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). var viewport = renderContext.ViewportState.Viewport0; - foreach (var renderUIElement in RenderObjects) + foreach (var renderPage in RenderObjects) { - UIElement loopedElementUnderMouseCursor = null; - - Matrix worldViewProjection = GetWorldViewProjection(renderUIElement, cameraSlot.Camera, renderTarget); + Matrix worldViewProjection = GetWorldViewProjection(renderPage, cameraSlot.Camera, renderTarget); + // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) { - PickingUpdate(renderUIElement, viewport, ref worldViewProjection, gameTime, ref loopedElementUnderMouseCursor); + UIElement renderPageElementUnderPointer = null; + UpdateRenderPagePointerInput(renderPage, viewport, ref worldViewProjection, gameTime, ref renderPageElementUnderPointer); // only update result element, when this one has a value - if (loopedElementUnderMouseCursor != null) - elementUnderMouseCursor = loopedElementUnderMouseCursor; + if (renderPageElementUnderPointer != null) + elementUnderPointer = renderPageElementUnderPointer; } } } PickingClear(); - return elementUnderMouseCursor; + UIElementUnderMouseCursor = elementUnderPointer; } - private void UpdateStates() + private void UpdateUIInputStates() { - oldRenderObjects.AddRange(inputStates.Keys); - foreach (RenderUIElement renderUIElement in RenderObjects) + previousRenderObjects.AddRange(inputStates.Keys); + + foreach (RenderUIPage renderUIElement in RenderObjects) { - // Create a state for the render object if it doesn't have one already. if (!inputStates.TryGetValue(renderUIElement, out var state)) inputStates[renderUIElement] = state = new UIElementInputState(); // Remove the current render object, so only render objects that have state but have been removed from the render feature are left. - oldRenderObjects.Remove(renderUIElement); + previousRenderObjects.Remove(renderUIElement); } // Remove the state for all remaining render objects, because they are no longer on the render feature. - foreach (RenderUIElement unhandledElement in oldRenderObjects) + foreach (RenderUIPage unhandledElement in previousRenderObjects) { inputStates.Remove(unhandledElement); } } - private void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, ref Matrix worldViewProj, GameTime drawTime, ref UIElement elementUnderMouseCursor) - + private void UpdateRenderPagePointerInput(RenderUIPage renderUIPage, Viewport viewport, ref Matrix worldViewProj, GameTime gameTime, ref UIElement elementUnderPointer) { - if (renderUIElement.Page?.RootElement == null) + if (renderUIPage.Page?.RootElement == null) return; var inverseZViewProj = worldViewProj; inverseZViewProj.Row3 = -inverseZViewProj.Row3; - elementUnderMouseCursor = UpdateMouseOver(ref viewport, ref inverseZViewProj, renderUIElement); - UpdateTouchEvents(ref viewport, ref inverseZViewProj, renderUIElement, drawTime); - } - - private void PickingClear() - { - // clear the list of compacted pointer events of time frame - ClearPointerEvents(); + elementUnderPointer = UpdateMouseOver(ref viewport, ref inverseZViewProj, renderUIPage); + UpdateTouchEvents(ref viewport, ref inverseZViewProj, renderUIPage, gameTime); } private void PickingPrepare() @@ -118,6 +111,12 @@ private void PickingPrepare() // compact all the pointer events that happened since last frame to avoid performing useless hit tests. CompactPointerEvents(); } + + private void PickingClear() + { + // clear the list of compacted pointer events of time frame + ClearPointerEvents(); + } private void CompactPointerEvents() { @@ -153,6 +152,34 @@ private void ClearPointerEvents() compactedPointerEvents.Clear(); } + /// + /// Gets a ray from a position in screen space if it is within the bounds of the resolution. + /// + /// The bounds to test within + /// The in which the component is being rendered + /// + /// The position of the lick on the screen in normalized (0..1, 0..1) range + /// from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range + /// + private bool TryGetRenderPageRay(Vector3 resolution, ref Viewport viewport, ref Matrix worldViewProj, Vector2 screenPosition, out Ray uiRay) + { + uiRay = new Ray(new Vector3(float.NegativeInfinity), new Vector3(0, 1, 0)); + + // TODO XK-3367 This only works for a single view + + // Get a ray in object (RenderUIElement) space + var ray = GetWorldRay(ref viewport, screenPosition, ref worldViewProj); + + // If the screen point is outside the canvas ignore any further testing + var dist = -ray.Position.Z / ray.Direction.Z; + if (Math.Abs(ray.Position.X + ray.Direction.X * dist) > resolution.X * 0.5f || + Math.Abs(ray.Position.Y + ray.Direction.Y * dist) > resolution.Y * 0.5f) + return false; + + uiRay = ray; + return true; + } + /// /// Creates a ray in object space based on a screen position and a previously rendered object's WorldViewProjection matrix /// @@ -169,46 +196,17 @@ private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix wor screenPos.Y *= GraphicsDevice.Presenter.BackBuffer.Height; var unprojectedNear = viewport.Unproject(new Vector3(screenPos, 0.0f), ref worldViewProj); - var unprojectedFar = viewport.Unproject(new Vector3(screenPos, 1.0f), ref worldViewProj); var rayDirection = Vector3.Normalize(unprojectedFar - unprojectedNear); - var clickRay = new Ray(unprojectedNear, rayDirection); - - return clickRay; - } - - /// - /// Returns if a screen position is within the borders of a tested ui component - /// - /// The to be tested - /// The in which the component is being rendered - /// - /// The position of the lick on the screen in normalized (0..1, 0..1) range - /// from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range - /// - private bool GetTouchPosition(Vector3 resolution, ref Viewport viewport, ref Matrix worldViewProj, Vector2 screenPosition, out Ray uiRay) - { - uiRay = new Ray(new Vector3(float.NegativeInfinity), new Vector3(0, 1, 0)); - - // TODO XK-3367 This only works for a single view - - // Get a touch ray in object (UI component) space - var touchRay = GetWorldRay(ref viewport, screenPosition, ref worldViewProj); + var uiRay = new Ray(unprojectedNear, rayDirection); - // If the click point is outside the canvas ignore any further testing - var dist = -touchRay.Position.Z / touchRay.Direction.Z; - if (Math.Abs(touchRay.Position.X + touchRay.Direction.X * dist) > resolution.X * 0.5f || - Math.Abs(touchRay.Position.Y + touchRay.Direction.Y * dist) > resolution.Y * 0.5f) - return false; - - uiRay = touchRay; - return true; + return uiRay; } - private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, RenderUIElement state, GameTime gameTime) + private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, RenderUIPage renderPage, GameTime gameTime) { - var rootElement = state.Page.RootElement; + var rootElement = renderPage.Page.RootElement; var intersectionPoint = Vector3.Zero; var lastTouchPosition = new Vector2(float.NegativeInfinity); @@ -216,7 +214,7 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, foreach (var pointerEvent in compactedPointerEvents) { // performance optimization: skip all the events that started outside of the UI - var lastTouchedElement = state.LastTouchedElement; + var lastTouchedElement = renderPage.LastTouchedElement; if (lastTouchedElement == null && pointerEvent.EventType != PointerEventType.Pressed) continue; @@ -229,14 +227,14 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, if (lastTouchPosition != currentTouchPosition) { Ray uiRay; - if (!GetTouchPosition(state.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) + if (!TryGetRenderPageRay(renderPage.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) continue; currentTouchedElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); } if (pointerEvent.EventType == PointerEventType.Pressed || pointerEvent.EventType == PointerEventType.Released) - state.LastIntersectionPoint = intersectionPoint; + renderPage.LastIntersectionPoint = intersectionPoint; // TODO: add the pointer type to the event args? var touchEvent = new TouchEventArgs @@ -246,7 +244,7 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, ScreenPosition = currentTouchPosition, ScreenTranslation = pointerEvent.DeltaPosition, WorldPosition = intersectionPoint, - WorldTranslation = intersectionPoint - state.LastIntersectionPoint + WorldTranslation = intersectionPoint - renderPage.LastIntersectionPoint }; switch (pointerEvent.EventType) @@ -299,42 +297,38 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, } lastTouchPosition = currentTouchPosition; - state.LastTouchedElement = currentTouchedElement; - state.LastIntersectionPoint = intersectionPoint; + renderPage.LastTouchedElement = currentTouchedElement; + renderPage.LastIntersectionPoint = intersectionPoint; } } - private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewProj, RenderUIElement state) + private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewProj, RenderUIPage renderPage) { if (input == null || !input.HasMouse) return null; var intersectionPoint = Vector3.Zero; var mousePosition = input.MousePosition; - var rootElement = state.Page.RootElement; - var lastMouseOverElement = state.LastMouseOverElement; - - UIElement mouseOverElement = lastMouseOverElement; - + var rootElement = renderPage.Page.RootElement; + var lastPointerOverElement = renderPage.LastPointerOverElement; + UIElement mouseOverElement = lastPointerOverElement; + // determine currently overred element. - if (mousePosition != state.LastMousePosition - || (lastMouseOverElement?.RequiresMouseOverUpdate ?? false)) + if (mousePosition != renderPage.LastMousePosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) { Ray uiRay; - if (!GetTouchPosition(state.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) + if (!TryGetRenderPageRay(renderPage.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) return null; mouseOverElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); - - } // find the common parent between current and last overred elements - var commonElement = FindCommonParent(mouseOverElement, lastMouseOverElement); + var commonElement = FindCommonParent(mouseOverElement, lastPointerOverElement); // disable mouse over state to previously overred hierarchy - var parent = lastMouseOverElement; + var parent = lastPointerOverElement; while (parent != commonElement && parent != null) { parent.RequiresMouseOverUpdate = false; @@ -362,8 +356,8 @@ private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewPro } // update cached values - state.LastMouseOverElement = mouseOverElement; - state.LastMousePosition = mousePosition; + renderPage.LastPointerOverElement = mouseOverElement; + renderPage.LastMousePosition = mousePosition; return mouseOverElement; } @@ -520,37 +514,37 @@ private static void PerformRecursiveHitTest(UIElement element, ref Ray ray, ref PerformRecursiveHitTest(child, ref ray, ref worldViewProj, results); } - public static Matrix GetWorldViewProjection(RenderUIElement renderUIElement, CameraComponent camera, Texture renderTarget) + public static Matrix GetWorldViewProjection(RenderUIPage renderUIPage, CameraComponent camera, Texture renderTarget) { Matrix worldViewProjection = Matrix.Identity; // calculate the size of the virtual resolution depending on target size (UI canvas) - var virtualResolution = renderUIElement.Resolution; + var virtualResolution = renderUIPage.Resolution; - if (renderUIElement.IsFullScreen) + if (renderUIPage.IsFullScreen) { //var targetSize = viewportSize; var targetSize = new Vector2(renderTarget.Width, renderTarget.Height); // update the virtual resolution of the renderer - if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) + if (renderUIPage.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) virtualResolution.Y = virtualResolution.X * targetSize.Y / targetSize.X; - if (renderUIElement.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) + if (renderUIPage.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) virtualResolution.X = virtualResolution.Y * targetSize.X / targetSize.Y; - worldViewProjection = GetWorldViewProjection(renderUIElement, virtualResolution); + worldViewProjection = GetWorldViewProjection(renderUIPage, virtualResolution); } else { //var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); if (camera != null) - worldViewProjection = GetWorldViewProjection(renderUIElement, camera); + worldViewProjection = GetWorldViewProjection(renderUIPage, camera); } return worldViewProjection; } - private static Matrix GetWorldViewProjection(RenderUIElement renderObject, CameraComponent camera) + private static Matrix GetWorldViewProjection(RenderUIPage renderObject, CameraComponent camera) { var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); @@ -617,7 +611,7 @@ private static Matrix GetWorldViewProjection(RenderUIElement renderObject, Camer return worldViewProjectionMatrix; } - private static Matrix GetWorldViewProjection(RenderUIElement renderObject, Vector3 virtualResolution) + private static Matrix GetWorldViewProjection(RenderUIPage renderObject, Vector3 virtualResolution) { var nearPlane = virtualResolution.Z / 2; var farPlane = nearPlane + virtualResolution.Z; diff --git a/sources/engine/Stride.UI/UISystem.cs b/sources/engine/Stride.UI/UISystem.cs index b0ba8a825e..c7bce464f9 100644 --- a/sources/engine/Stride.UI/UISystem.cs +++ b/sources/engine/Stride.UI/UISystem.cs @@ -130,12 +130,12 @@ public override void Update(GameTime gameTime) if (renderContext == null) renderContext = RenderContext.GetShared(Services); - UIElementUnderMouseCursor = Pick(gameTime); + Pick(gameTime); UpdateKeyEvents(); } - private partial UIElement Pick(GameTime gameTime); + private partial void Pick(GameTime gameTime); private void UpdateKeyEvents() { From 8b5c617da289c33651d83d39cad0f36e25f1ed2f Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Sun, 29 Sep 2024 11:46:12 -0700 Subject: [PATCH 09/20] Refactored UISystem to no longer use render objects Added a UIDocument class that acts much the same way, but also can store state input state, removing the need to manually create it in the UISystem. Also will allow the UIRenderFeature and UISystem to be completely independent later on after Batch is removed from the UISystem. --- .../engine/Stride.UI/Engine/UIComponent.cs | 2 + .../Stride.UI/Rendering/UI/RenderUIPage.cs | 25 +--- .../Stride.UI/Rendering/UI/UIRenderFeature.cs | 9 +- .../Rendering/UI/UIRenderProcessor.cs | 1 - .../engine/Stride.UI/UIComponentProcessor.cs | 71 +++++++++++ sources/engine/Stride.UI/UIDocument.cs | 61 ++++++++++ sources/engine/Stride.UI/UISystem.Picking.cs | 113 ++++++++---------- 7 files changed, 189 insertions(+), 93 deletions(-) create mode 100644 sources/engine/Stride.UI/UIComponentProcessor.cs create mode 100644 sources/engine/Stride.UI/UIDocument.cs diff --git a/sources/engine/Stride.UI/Engine/UIComponent.cs b/sources/engine/Stride.UI/Engine/UIComponent.cs index d2a399285a..a0a3a83e39 100644 --- a/sources/engine/Stride.UI/Engine/UIComponent.cs +++ b/sources/engine/Stride.UI/Engine/UIComponent.cs @@ -7,6 +7,7 @@ using Stride.Engine.Design; using Stride.Rendering; using Stride.Rendering.UI; +using Stride.UI; namespace Stride.Engine { @@ -16,6 +17,7 @@ namespace Stride.Engine [DataContract("UIComponent")] [Display("UI", Expand = ExpandRule.Once)] [DefaultEntityComponentRenderer(typeof(UIRenderProcessor))] + [DefaultEntityComponentProcessor(typeof(UIComponentProcessor))] [ComponentOrder(9800)] [ComponentCategory("UI")] public sealed class UIComponent : ActivableEntityComponent diff --git a/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs b/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs index 69fd2a6b34..e0b44faf63 100644 --- a/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs +++ b/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs @@ -3,10 +3,12 @@ using Stride.Core; using Stride.Core.Mathematics; using Stride.Engine; -using Stride.UI; namespace Stride.Rendering.UI { + /// + /// The texture to use when rendering the UI. + /// public enum UIElementSampler { [Display("Point (Nearest)")] @@ -21,10 +23,6 @@ public enum UIElementSampler public class RenderUIPage : RenderObject { - public RenderUIPage() - { - } - public Matrix WorldMatrix; // UIComponent values @@ -38,23 +36,6 @@ public RenderUIPage() public bool SnapText; public bool IsFixedSize; - /// - /// Last registered position of teh mouse - /// - public Vector2 LastMousePosition; - - /// - /// Last element over which the mouse cursor was registered - /// - public UIElement LastPointerOverElement; - - /// - /// Last element which received a touch/click event - /// - public UIElement LastTouchedElement; - - public Vector3 LastIntersectionPoint; - public Matrix LastRootMatrix; } } diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index cf89029f66..600efb7cd0 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -49,6 +49,7 @@ protected override void InitializeCore() game = RenderSystem.Services.GetService(); uiSystem = RenderSystem.Services.GetService(); + // TODO: Change Batch to be local to the UIRenderFeature and remove dep on UISystem. Requires refactoring ElementRenderer. if (uiSystem == null) { var gameSystems = RenderSystem.Services.GetSafeServiceAs(); @@ -60,7 +61,7 @@ protected override void InitializeCore() rendererManager = new RendererManager(new DefaultRenderersFactory(RenderSystem.Services)); - batch = uiSystem.Batch; + batch = uiSystem.Batch; } public override void Draw(RenderDrawContext context, RenderView renderView, RenderViewStage renderViewStage, int startIndex, int endIndex) @@ -147,12 +148,6 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend uiElementState.Update(renderObject, cameraComponent); } } - - // Handle input. - //UIElementUnderMouseCursor = uiInput?.Pick(drawTime); - - uiSystem.RenderObjects.UnionWith(RenderObjects.OfType()); - // render the UI elements of all the entities foreach (var uiElementState in uiElementStates) diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs index 7d9624b7d3..f55eddb1a7 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Stride.Engine; -using Stride.Rendering; namespace Stride.Rendering.UI { diff --git a/sources/engine/Stride.UI/UIComponentProcessor.cs b/sources/engine/Stride.UI/UIComponentProcessor.cs new file mode 100644 index 0000000000..08b690322d --- /dev/null +++ b/sources/engine/Stride.UI/UIComponentProcessor.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Stride.Engine; +using Stride.Games; + +namespace Stride.UI; + +/// +/// The processor in charge of updating entities that have s. +/// +public class UIComponentProcessor : EntityProcessor +{ + private UISystem uiSystem; + + public UIComponentProcessor() : base(typeof(TransformComponent)) + { + + } + + protected override void OnSystemAdd() + { + uiSystem = Services.GetService(); + if (uiSystem == null) + { + uiSystem = new UISystem(Services); + Services.AddService(uiSystem); + var gameSystems = Services.GetService(); + gameSystems?.Add(uiSystem); + } + } + + public override void Update(GameTime time) + { + // Update all the UI documents that are created from UIComponents to match their values. + foreach (var componentDocumentKeyPair in ComponentDatas) + { + var uiComponent = componentDocumentKeyPair.Key; + var uiDocument = componentDocumentKeyPair.Value; + + uiDocument.Enabled = uiComponent.Enabled; + + uiDocument.WorldMatrix = uiComponent.Entity.Transform.WorldMatrix; + uiDocument.RenderGroup = uiComponent.RenderGroup; + uiDocument.Page = uiComponent.Page; + uiDocument.Sampler = uiComponent.Sampler; + uiDocument.IsFullScreen = uiComponent.IsFullScreen; + uiDocument.Resolution = uiComponent.Resolution; + uiDocument.Size = uiComponent.Size; + uiDocument.ResolutionStretch = uiComponent.ResolutionStretch; + uiDocument.IsBillboard = uiComponent.IsBillboard; + uiDocument.SnapText = uiComponent.SnapText; + uiDocument.IsFixedSize = uiComponent.IsFixedSize; + } + } + + protected override void OnEntityComponentAdding(Entity entity, UIComponent uiComponent, UIDocument uiDocument) + { + uiSystem.AddDocument(uiDocument); + } + + protected override void OnEntityComponentRemoved(Entity entity, UIComponent uiComponent, UIDocument uiDocument) + { + uiSystem.RemoveDocument(uiDocument); + } + + protected override UIDocument GenerateComponentData(Entity entity, UIComponent component) + { + return new UIDocument(); + } +} diff --git a/sources/engine/Stride.UI/UIDocument.cs b/sources/engine/Stride.UI/UIDocument.cs new file mode 100644 index 0000000000..d5ac253026 --- /dev/null +++ b/sources/engine/Stride.UI/UIDocument.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Stride.Core.Mathematics; +using Stride.Engine; +using Stride.Rendering; +using Stride.Rendering.UI; + +namespace Stride.UI; + +/// +/// Describes a UI and the data for displaying and interacting with it in . +/// +public class UIDocument +{ + /// + /// Whether the UI is active and will be rendered and interacted with by user input. + /// + public bool Enabled { get; set; } + + /// + /// A world space matrix that describes the position, rotation, and scale of the UI in a scene when the UI is not a FullScreen UI. + /// + public Matrix WorldMatrix { get; set; } + + /// + /// The group to render the UI in. + /// + public RenderGroup RenderGroup { get; set; } + + /// + /// A page containing the hierarchy that describes the UI. + /// + public UIPage Page { get; set; } + + public UIElementSampler Sampler { get; set; } + public bool IsFullScreen { get; set; } + public Vector3 Resolution { get; set; } + public Vector3 Size { get; set; } + public ResolutionStretch ResolutionStretch { get; set; } + public bool IsBillboard { get; set; } + public bool SnapText { get; set; } + public bool IsFixedSize { get; set; } + + /// + /// Last registered position of the mouse + /// + public Vector2 LastMousePosition { get; set; } + + /// + /// Last element over which the mouse cursor was registered + /// + public UIElement LastPointerOverElement { get; set; } + + /// + /// Last element which received a touch/click event + /// + public UIElement LastTouchedElement { get; set; } + + public Vector3 LastIntersectionPoint { get; set; } +} diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index 091f2502c1..7d94521779 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Stride.Core; +using Stride.Core.Collections; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; using Stride.Engine; @@ -21,10 +22,9 @@ public partial class UISystem // object to avoid allocation at each element leave event private readonly HashSet newlySelectedElementParents = new HashSet(); private readonly List compactedPointerEvents = new List(); - private readonly Dictionary inputStates = new Dictionary(); private readonly List previousRenderObjects = new List(); - - public HashSet RenderObjects { get; } = new HashSet(); + + private TrackingHashSet documents = new TrackingHashSet(); /// /// Represents the UI-element that's currently under the mouse cursor. @@ -33,6 +33,16 @@ public partial class UISystem /// public UIElement UIElementUnderMouseCursor { get; internal set; } + public bool AddDocument(UIDocument document) + { + return documents.Add(document); + } + + public bool RemoveDocument(UIDocument document) + { + return documents.Add(document); + } + private partial void Pick(GameTime gameTime) { if (renderContext == null || sceneSystem == null || sceneSystem.GraphicsCompositor == null) @@ -41,8 +51,6 @@ private partial void Pick(GameTime gameTime) Texture renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; UIElement elementUnderPointer = null; - UpdateUIInputStates(); - // Prepare content required for Picking and MouseOver events PickingPrepare(); @@ -51,15 +59,15 @@ private partial void Pick(GameTime gameTime) // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). var viewport = renderContext.ViewportState.Viewport0; - foreach (var renderPage in RenderObjects) + foreach (var uiDocument in documents) { - Matrix worldViewProjection = GetWorldViewProjection(renderPage, cameraSlot.Camera, renderTarget); + Matrix worldViewProjection = GetWorldViewProjection(uiDocument, cameraSlot.Camera, renderTarget); // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) { UIElement renderPageElementUnderPointer = null; - UpdateRenderPagePointerInput(renderPage, viewport, ref worldViewProjection, gameTime, ref renderPageElementUnderPointer); + UpdateRenderPagePointerInput(uiDocument, viewport, ref worldViewProjection, gameTime, ref renderPageElementUnderPointer); // only update result element, when this one has a value if (renderPageElementUnderPointer != null) @@ -72,38 +80,17 @@ private partial void Pick(GameTime gameTime) UIElementUnderMouseCursor = elementUnderPointer; } - - private void UpdateUIInputStates() - { - previousRenderObjects.AddRange(inputStates.Keys); - - foreach (RenderUIPage renderUIElement in RenderObjects) - { - // Create a state for the render object if it doesn't have one already. - if (!inputStates.TryGetValue(renderUIElement, out var state)) - inputStates[renderUIElement] = state = new UIElementInputState(); - - // Remove the current render object, so only render objects that have state but have been removed from the render feature are left. - previousRenderObjects.Remove(renderUIElement); - } - - // Remove the state for all remaining render objects, because they are no longer on the render feature. - foreach (RenderUIPage unhandledElement in previousRenderObjects) - { - inputStates.Remove(unhandledElement); - } - } - private void UpdateRenderPagePointerInput(RenderUIPage renderUIPage, Viewport viewport, ref Matrix worldViewProj, GameTime gameTime, ref UIElement elementUnderPointer) + private void UpdateRenderPagePointerInput(UIDocument uiDocument, Viewport viewport, ref Matrix worldViewProj, GameTime gameTime, ref UIElement elementUnderPointer) { - if (renderUIPage.Page?.RootElement == null) + if (uiDocument.Page?.RootElement == null) return; var inverseZViewProj = worldViewProj; inverseZViewProj.Row3 = -inverseZViewProj.Row3; - elementUnderPointer = UpdateMouseOver(ref viewport, ref inverseZViewProj, renderUIPage); - UpdateTouchEvents(ref viewport, ref inverseZViewProj, renderUIPage, gameTime); + elementUnderPointer = UpdateMouseOver(ref viewport, ref inverseZViewProj, uiDocument); + UpdateTouchEvents(ref viewport, ref inverseZViewProj, uiDocument, gameTime); } private void PickingPrepare() @@ -204,9 +191,9 @@ private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix wor return uiRay; } - private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, RenderUIPage renderPage, GameTime gameTime) + private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, UIDocument uiDocument, GameTime gameTime) { - var rootElement = renderPage.Page.RootElement; + var rootElement = uiDocument.Page.RootElement; var intersectionPoint = Vector3.Zero; var lastTouchPosition = new Vector2(float.NegativeInfinity); @@ -214,7 +201,7 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, foreach (var pointerEvent in compactedPointerEvents) { // performance optimization: skip all the events that started outside of the UI - var lastTouchedElement = renderPage.LastTouchedElement; + var lastTouchedElement = uiDocument.LastTouchedElement; if (lastTouchedElement == null && pointerEvent.EventType != PointerEventType.Pressed) continue; @@ -227,14 +214,14 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, if (lastTouchPosition != currentTouchPosition) { Ray uiRay; - if (!TryGetRenderPageRay(renderPage.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) + if (!TryGetRenderPageRay(uiDocument.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) continue; currentTouchedElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); } if (pointerEvent.EventType == PointerEventType.Pressed || pointerEvent.EventType == PointerEventType.Released) - renderPage.LastIntersectionPoint = intersectionPoint; + uiDocument.LastIntersectionPoint = intersectionPoint; // TODO: add the pointer type to the event args? var touchEvent = new TouchEventArgs @@ -244,7 +231,7 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, ScreenPosition = currentTouchPosition, ScreenTranslation = pointerEvent.DeltaPosition, WorldPosition = intersectionPoint, - WorldTranslation = intersectionPoint - renderPage.LastIntersectionPoint + WorldTranslation = intersectionPoint - uiDocument.LastIntersectionPoint }; switch (pointerEvent.EventType) @@ -297,28 +284,28 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, } lastTouchPosition = currentTouchPosition; - renderPage.LastTouchedElement = currentTouchedElement; - renderPage.LastIntersectionPoint = intersectionPoint; + uiDocument.LastTouchedElement = currentTouchedElement; + uiDocument.LastIntersectionPoint = intersectionPoint; } } - private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewProj, RenderUIPage renderPage) + private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewProj, UIDocument uiDocument) { if (input == null || !input.HasMouse) return null; var intersectionPoint = Vector3.Zero; var mousePosition = input.MousePosition; - var rootElement = renderPage.Page.RootElement; - var lastPointerOverElement = renderPage.LastPointerOverElement; + var rootElement = uiDocument.Page.RootElement; + var lastPointerOverElement = uiDocument.LastPointerOverElement; UIElement mouseOverElement = lastPointerOverElement; // determine currently overred element. - if (mousePosition != renderPage.LastMousePosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) + if (mousePosition != uiDocument.LastMousePosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) { Ray uiRay; - if (!TryGetRenderPageRay(renderPage.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) + if (!TryGetRenderPageRay(uiDocument.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) return null; mouseOverElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); @@ -356,8 +343,8 @@ private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewPro } // update cached values - renderPage.LastPointerOverElement = mouseOverElement; - renderPage.LastMousePosition = mousePosition; + uiDocument.LastPointerOverElement = mouseOverElement; + uiDocument.LastMousePosition = mousePosition; return mouseOverElement; } @@ -514,44 +501,44 @@ private static void PerformRecursiveHitTest(UIElement element, ref Ray ray, ref PerformRecursiveHitTest(child, ref ray, ref worldViewProj, results); } - public static Matrix GetWorldViewProjection(RenderUIPage renderUIPage, CameraComponent camera, Texture renderTarget) + public static Matrix GetWorldViewProjection(UIDocument uiDocument, CameraComponent camera, Texture renderTarget) { Matrix worldViewProjection = Matrix.Identity; // calculate the size of the virtual resolution depending on target size (UI canvas) - var virtualResolution = renderUIPage.Resolution; + var virtualResolution = uiDocument.Resolution; - if (renderUIPage.IsFullScreen) + if (uiDocument.IsFullScreen) { //var targetSize = viewportSize; var targetSize = new Vector2(renderTarget.Width, renderTarget.Height); // update the virtual resolution of the renderer - if (renderUIPage.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) + if (uiDocument.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) virtualResolution.Y = virtualResolution.X * targetSize.Y / targetSize.X; - if (renderUIPage.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) + if (uiDocument.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) virtualResolution.X = virtualResolution.Y * targetSize.X / targetSize.Y; - worldViewProjection = GetWorldViewProjection(renderUIPage, virtualResolution); + worldViewProjection = GetWorldViewProjection(uiDocument, virtualResolution); } else { //var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); if (camera != null) - worldViewProjection = GetWorldViewProjection(renderUIPage, camera); + worldViewProjection = GetWorldViewProjection(uiDocument, camera); } return worldViewProjection; } - private static Matrix GetWorldViewProjection(RenderUIPage renderObject, CameraComponent camera) + private static Matrix GetWorldViewProjection(UIDocument uiDocument, CameraComponent camera) { var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); - var worldMatrix = renderObject.WorldMatrix; + var worldMatrix = uiDocument.WorldMatrix; // rotate the UI element perpendicular to the camera view vector, if billboard is activated - if (renderObject.IsFullScreen) + if (uiDocument.IsFullScreen) { worldMatrix = Matrix.Identity; } @@ -561,7 +548,7 @@ private static Matrix GetWorldViewProjection(RenderUIPage renderObject, CameraCo Matrix.Invert(ref camera.ViewMatrix, out viewInverse); var forwardVector = viewInverse.Forward; - if (renderObject.IsBillboard) + if (uiDocument.IsBillboard) { var viewInverseRow1 = viewInverse.Row1; var viewInverseRow2 = viewInverse.Row2; @@ -580,7 +567,7 @@ private static Matrix GetWorldViewProjection(RenderUIPage renderObject, CameraCo worldMatrix.Row3 = viewInverse.Row3; } - if (renderObject.IsFixedSize) + if (uiDocument.IsFixedSize) { forwardVector.Normalize(); var distVec = (worldMatrix.TranslationVector - viewInverse.TranslationVector); @@ -596,7 +583,7 @@ private static Matrix GetWorldViewProjection(RenderUIPage renderObject, CameraCo } // If the UI component is not drawn fullscreen it should be drawn as a quad with world sizes corresponding to its actual size - worldMatrix = Matrix.Scaling(renderObject.Size / renderObject.Resolution) * worldMatrix; + worldMatrix = Matrix.Scaling(uiDocument.Size / uiDocument.Resolution) * worldMatrix; } // Rotation of Pi along 0x to go from UI space to world space @@ -611,7 +598,7 @@ private static Matrix GetWorldViewProjection(RenderUIPage renderObject, CameraCo return worldViewProjectionMatrix; } - private static Matrix GetWorldViewProjection(RenderUIPage renderObject, Vector3 virtualResolution) + private static Matrix GetWorldViewProjection(UIDocument uiDocument, Vector3 virtualResolution) { var nearPlane = virtualResolution.Z / 2; var farPlane = nearPlane + virtualResolution.Z; @@ -628,7 +615,7 @@ private static Matrix GetWorldViewProjection(RenderUIPage renderObject, Vector3 ProjectionMatrix = Matrix.PerspectiveFovRH(verticalFov, aspectRatio, nearPlane, farPlane), }; - return GetWorldViewProjection(renderObject, cameraComponent); + return GetWorldViewProjection(uiDocument, cameraComponent); } /// From 04483effa0b2070d4b009f4ad4797721a01cdf7c Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Sun, 29 Sep 2024 13:15:22 -0700 Subject: [PATCH 10/20] Small cleanup refactor in UI and moved methods to UIDocumentExtensions --- .../{RenderUIPage.cs => RenderUIDocument.cs} | 2 +- .../Stride.UI/Rendering/UI/UIRenderFeature.cs | 12 +- .../Rendering/UI/UIRenderProcessor.cs | 20 +-- .../engine/Stride.UI/UIDocumentExtensions.cs | 129 ++++++++++++++ sources/engine/Stride.UI/UISystem.Picking.cs | 167 +++--------------- 5 files changed, 172 insertions(+), 158 deletions(-) rename sources/engine/Stride.UI/Rendering/UI/{RenderUIPage.cs => RenderUIDocument.cs} (95%) create mode 100644 sources/engine/Stride.UI/UIDocumentExtensions.cs diff --git a/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs b/sources/engine/Stride.UI/Rendering/UI/RenderUIDocument.cs similarity index 95% rename from sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs rename to sources/engine/Stride.UI/Rendering/UI/RenderUIDocument.cs index e0b44faf63..d6d61c22a0 100644 --- a/sources/engine/Stride.UI/Rendering/UI/RenderUIPage.cs +++ b/sources/engine/Stride.UI/Rendering/UI/RenderUIDocument.cs @@ -21,7 +21,7 @@ public enum UIElementSampler AnisotropicClamp, } - public class RenderUIPage : RenderObject + public class RenderUIDocument : RenderObject { public Matrix WorldMatrix; diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index 600efb7cd0..c8bf2f58c0 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -33,7 +33,7 @@ public partial class UIRenderFeature : RootRenderFeature private readonly List uiElementStates = new List(); - public override Type SupportedRenderObjectType => typeof(RenderUIPage); + public override Type SupportedRenderObjectType => typeof(RenderUIDocument); public UIRenderFeature() @@ -90,7 +90,7 @@ private void DrawInternal(RenderDrawContext context, RenderView renderView, Rend { var renderNodeReference = renderViewStage.SortedRenderNodes[index].RenderNode; var renderNode = GetRenderNode(renderNodeReference); - var renderElement = (RenderUIPage)renderNode.RenderObject; + var renderElement = (RenderUIDocument)renderNode.RenderObject; uiElementStates.Add(new UIElementState(renderElement)); } @@ -318,16 +318,16 @@ public void RegisterRenderer(UIElement element, ElementRenderer renderer) private class UIElementState { - public readonly RenderUIPage RenderObject; + public readonly RenderUIDocument RenderObject; public Matrix WorldViewProjectionMatrix; - public UIElementState(RenderUIPage renderObject) + public UIElementState(RenderUIDocument renderObject) { RenderObject = renderObject; WorldViewProjectionMatrix = Matrix.Identity; } - public void Update(RenderUIPage renderObject, CameraComponent camera) + public void Update(RenderUIDocument renderObject, CameraComponent camera) { var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); @@ -391,7 +391,7 @@ public void Update(RenderUIPage renderObject, CameraComponent camera) Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out WorldViewProjectionMatrix); } - public void Update(RenderUIPage renderObject, Vector3 virtualResolution) + public void Update(RenderUIDocument renderObject, Vector3 virtualResolution) { var nearPlane = virtualResolution.Z / 2; var farPlane = nearPlane + virtualResolution.Z; diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs index f55eddb1a7..89fb3d2f53 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderProcessor.cs @@ -9,9 +9,9 @@ namespace Stride.Rendering.UI /// /// The processor in charge of updating and drawing the entities having UI components. /// - public class UIRenderProcessor : EntityProcessor, IEntityComponentRenderProcessor + public class UIRenderProcessor : EntityProcessor, IEntityComponentRenderProcessor { - public List UIRoots { get; private set; } + public List UIRoots { get; private set; } public VisibilityGroup VisibilityGroup { get; set; } @@ -21,7 +21,7 @@ public class UIRenderProcessor : EntityProcessor, IEn public UIRenderProcessor() : base(typeof(TransformComponent)) { - UIRoots = new List(); + UIRoots = new List(); } public override void Draw(RenderContext gameTime) @@ -58,22 +58,22 @@ public override void Draw(RenderContext gameTime) } } - protected override void OnEntityComponentAdding(Entity entity, UIComponent uiComponent, RenderUIPage renderUIPage) + protected override void OnEntityComponentAdding(Entity entity, UIComponent uiComponent, RenderUIDocument renderUIDocument) { - VisibilityGroup.RenderObjects.Add(renderUIPage); + VisibilityGroup.RenderObjects.Add(renderUIDocument); } - protected override void OnEntityComponentRemoved(Entity entity, UIComponent uiComponent, RenderUIPage renderUIPage) + protected override void OnEntityComponentRemoved(Entity entity, UIComponent uiComponent, RenderUIDocument renderUIDocument) { - VisibilityGroup.RenderObjects.Remove(renderUIPage); + VisibilityGroup.RenderObjects.Remove(renderUIDocument); } - protected override RenderUIPage GenerateComponentData(Entity entity, UIComponent component) + protected override RenderUIDocument GenerateComponentData(Entity entity, UIComponent component) { - return new RenderUIPage { Source = component }; + return new RenderUIDocument { Source = component }; } - protected override bool IsAssociatedDataValid(Entity entity, UIComponent component, RenderUIPage associatedData) + protected override bool IsAssociatedDataValid(Entity entity, UIComponent component, RenderUIDocument associatedData) { return associatedData.Source == component; } diff --git a/sources/engine/Stride.UI/UIDocumentExtensions.cs b/sources/engine/Stride.UI/UIDocumentExtensions.cs new file mode 100644 index 0000000000..fdf6f12733 --- /dev/null +++ b/sources/engine/Stride.UI/UIDocumentExtensions.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using Stride.Core.Mathematics; +using Stride.Engine; +using Stride.Graphics; +using Stride.Rendering; + +namespace Stride.UI; + +public static class UIDocumentExtensions +{ + public static Matrix GetWorldViewProjection(this UIDocument uiDocument, CameraComponent camera, Texture renderTarget) + { + Matrix worldViewProjection = Matrix.Identity; + + // calculate the size of the virtual resolution depending on target size (UI canvas) + var virtualResolution = uiDocument.Resolution; + + if (uiDocument.IsFullScreen) + { + //var targetSize = viewportSize; + var targetSize = new Vector2(renderTarget.Width, renderTarget.Height); + + // update the virtual resolution of the renderer + if (uiDocument.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) + virtualResolution.Y = virtualResolution.X * targetSize.Y / targetSize.X; + if (uiDocument.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) + virtualResolution.X = virtualResolution.Y * targetSize.X / targetSize.Y; + + worldViewProjection = GetWorldViewProjection(uiDocument, virtualResolution); + } + else + { + if (camera != null) + worldViewProjection = GetWorldViewProjection(uiDocument, camera); + } + + return worldViewProjection; + } + + private static Matrix GetWorldViewProjection(UIDocument uiDocument, Vector3 virtualResolution) + { + var nearPlane = virtualResolution.Z / 2; + var farPlane = nearPlane + virtualResolution.Z; + var zOffset = nearPlane + virtualResolution.Z / 2; + var aspectRatio = virtualResolution.X / virtualResolution.Y; + var verticalFov = MathF.Atan2(virtualResolution.Y / 2, zOffset) * 2; + + var cameraComponent = new CameraComponent(nearPlane, farPlane) + { + UseCustomAspectRatio = true, + AspectRatio = aspectRatio, + VerticalFieldOfView = MathUtil.RadiansToDegrees(verticalFov), + ViewMatrix = Matrix.LookAtRH(new Vector3(0, 0, zOffset), Vector3.Zero, Vector3.UnitY), + ProjectionMatrix = Matrix.PerspectiveFovRH(verticalFov, aspectRatio, nearPlane, farPlane), + }; + + return GetWorldViewProjection(uiDocument, cameraComponent); + } + + private static Matrix GetWorldViewProjection(UIDocument uiDocument, CameraComponent camera) + { + var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); + + var worldMatrix = uiDocument.WorldMatrix; + + // rotate the UI element perpendicular to the camera view vector, if billboard is activated + if (uiDocument.IsFullScreen) + { + worldMatrix = Matrix.Identity; + } + else + { + Matrix viewInverse; + Matrix.Invert(ref camera.ViewMatrix, out viewInverse); + var forwardVector = viewInverse.Forward; + + if (uiDocument.IsBillboard) + { + var viewInverseRow1 = viewInverse.Row1; + var viewInverseRow2 = viewInverse.Row2; + + // remove scale of the camera + viewInverseRow1 /= viewInverseRow1.XYZ().Length(); + viewInverseRow2 /= viewInverseRow2.XYZ().Length(); + + // set the scale of the object + viewInverseRow1 *= worldMatrix.Row1.XYZ().Length(); + viewInverseRow2 *= worldMatrix.Row2.XYZ().Length(); + + // set the adjusted world matrix + worldMatrix.Row1 = viewInverseRow1; + worldMatrix.Row2 = viewInverseRow2; + worldMatrix.Row3 = viewInverse.Row3; + } + + if (uiDocument.IsFixedSize) + { + forwardVector.Normalize(); + var distVec = (worldMatrix.TranslationVector - viewInverse.TranslationVector); + float distScalar; + Vector3.Dot(ref forwardVector, ref distVec, out distScalar); + distScalar = Math.Abs(distScalar); + + var worldScale = frustumHeight * distScalar * UIComponent.FixedSizeVerticalUnit; // FrustumHeight already is 2*Tan(FOV/2) + + worldMatrix.Row1 *= worldScale; + worldMatrix.Row2 *= worldScale; + worldMatrix.Row3 *= worldScale; + } + + // If the UI component is not drawn fullscreen it should be drawn as a quad with world sizes corresponding to its actual size + worldMatrix = Matrix.Scaling(uiDocument.Size / uiDocument.Resolution) * worldMatrix; + } + + // Rotation of Pi along 0x to go from UI space to world space + worldMatrix.Row2 = -worldMatrix.Row2; + worldMatrix.Row3 = -worldMatrix.Row3; + + Matrix worldViewMatrix; + Matrix worldViewProjectionMatrix; + Matrix.Multiply(ref worldMatrix, ref camera.ViewMatrix, out worldViewMatrix); + Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out worldViewProjectionMatrix); + + return worldViewProjectionMatrix; + } +} diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index 7d94521779..dbc708ba12 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -4,11 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Stride.Core; -using Stride.Core.Collections; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; -using Stride.Engine; using Stride.Games; using Stride.Graphics; using Stride.Input; @@ -22,9 +19,8 @@ public partial class UISystem // object to avoid allocation at each element leave event private readonly HashSet newlySelectedElementParents = new HashSet(); private readonly List compactedPointerEvents = new List(); - private readonly List previousRenderObjects = new List(); - private TrackingHashSet documents = new TrackingHashSet(); + private readonly HashSet documents = new HashSet(); /// /// Represents the UI-element that's currently under the mouse cursor. @@ -33,16 +29,6 @@ public partial class UISystem /// public UIElement UIElementUnderMouseCursor { get; internal set; } - public bool AddDocument(UIDocument document) - { - return documents.Add(document); - } - - public bool RemoveDocument(UIDocument document) - { - return documents.Add(document); - } - private partial void Pick(GameTime gameTime) { if (renderContext == null || sceneSystem == null || sceneSystem.GraphicsCompositor == null) @@ -61,7 +47,7 @@ private partial void Pick(GameTime gameTime) foreach (var uiDocument in documents) { - Matrix worldViewProjection = GetWorldViewProjection(uiDocument, cameraSlot.Camera, renderTarget); + Matrix worldViewProjection = uiDocument.GetWorldViewProjection(cameraSlot.Camera, renderTarget); // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) @@ -89,8 +75,8 @@ private void UpdateRenderPagePointerInput(UIDocument uiDocument, Viewport viewpo var inverseZViewProj = worldViewProj; inverseZViewProj.Row3 = -inverseZViewProj.Row3; - elementUnderPointer = UpdateMouseOver(ref viewport, ref inverseZViewProj, uiDocument); - UpdateTouchEvents(ref viewport, ref inverseZViewProj, uiDocument, gameTime); + elementUnderPointer = UpdateMouseOver(uiDocument, ref viewport, ref inverseZViewProj); + UpdateTouchEvents(uiDocument, ref viewport, ref inverseZViewProj, gameTime); } private void PickingPrepare() @@ -191,7 +177,7 @@ private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix wor return uiRay; } - private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, UIDocument uiDocument, GameTime gameTime) + private void UpdateTouchEvents(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj, GameTime gameTime) { var rootElement = uiDocument.Page.RootElement; var intersectionPoint = Vector3.Zero; @@ -289,7 +275,7 @@ private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, } } - private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewProj, UIDocument uiDocument) + private UIElement UpdateMouseOver(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj) { if (input == null || !input.HasMouse) return null; @@ -501,122 +487,31 @@ private static void PerformRecursiveHitTest(UIElement element, ref Ray ray, ref PerformRecursiveHitTest(child, ref ray, ref worldViewProj, results); } - public static Matrix GetWorldViewProjection(UIDocument uiDocument, CameraComponent camera, Texture renderTarget) + /// + /// Adds the specified to the to receive input events. + /// + /// The document to add. + /// true if the document is added to the ; false if the document is already present. + /// A render object needs to be added to the for the UI to be rendered. + public bool AddDocument(UIDocument document) { - Matrix worldViewProjection = Matrix.Identity; - - // calculate the size of the virtual resolution depending on target size (UI canvas) - var virtualResolution = uiDocument.Resolution; - - if (uiDocument.IsFullScreen) - { - //var targetSize = viewportSize; - var targetSize = new Vector2(renderTarget.Width, renderTarget.Height); - - // update the virtual resolution of the renderer - if (uiDocument.ResolutionStretch == ResolutionStretch.FixedWidthAdaptableHeight) - virtualResolution.Y = virtualResolution.X * targetSize.Y / targetSize.X; - if (uiDocument.ResolutionStretch == ResolutionStretch.FixedHeightAdaptableWidth) - virtualResolution.X = virtualResolution.Y * targetSize.X / targetSize.Y; - - worldViewProjection = GetWorldViewProjection(uiDocument, virtualResolution); - } - else - { - //var cameraComponent = renderContext.Tags.Get(CameraComponentRendererExtensions.Current); - if (camera != null) - worldViewProjection = GetWorldViewProjection(uiDocument, camera); - } - - return worldViewProjection; + return documents.Add(document); } - private static Matrix GetWorldViewProjection(UIDocument uiDocument, CameraComponent camera) - { - var frustumHeight = 2 * MathF.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); - - var worldMatrix = uiDocument.WorldMatrix; - - // rotate the UI element perpendicular to the camera view vector, if billboard is activated - if (uiDocument.IsFullScreen) - { - worldMatrix = Matrix.Identity; - } - else - { - Matrix viewInverse; - Matrix.Invert(ref camera.ViewMatrix, out viewInverse); - var forwardVector = viewInverse.Forward; - - if (uiDocument.IsBillboard) - { - var viewInverseRow1 = viewInverse.Row1; - var viewInverseRow2 = viewInverse.Row2; - - // remove scale of the camera - viewInverseRow1 /= viewInverseRow1.XYZ().Length(); - viewInverseRow2 /= viewInverseRow2.XYZ().Length(); - - // set the scale of the object - viewInverseRow1 *= worldMatrix.Row1.XYZ().Length(); - viewInverseRow2 *= worldMatrix.Row2.XYZ().Length(); - - // set the adjusted world matrix - worldMatrix.Row1 = viewInverseRow1; - worldMatrix.Row2 = viewInverseRow2; - worldMatrix.Row3 = viewInverse.Row3; - } - - if (uiDocument.IsFixedSize) - { - forwardVector.Normalize(); - var distVec = (worldMatrix.TranslationVector - viewInverse.TranslationVector); - float distScalar; - Vector3.Dot(ref forwardVector, ref distVec, out distScalar); - distScalar = Math.Abs(distScalar); - - var worldScale = frustumHeight * distScalar * UIComponent.FixedSizeVerticalUnit; // FrustumHeight already is 2*Tan(FOV/2) - - worldMatrix.Row1 *= worldScale; - worldMatrix.Row2 *= worldScale; - worldMatrix.Row3 *= worldScale; - } - - // If the UI component is not drawn fullscreen it should be drawn as a quad with world sizes corresponding to its actual size - worldMatrix = Matrix.Scaling(uiDocument.Size / uiDocument.Resolution) * worldMatrix; - } - - // Rotation of Pi along 0x to go from UI space to world space - worldMatrix.Row2 = -worldMatrix.Row2; - worldMatrix.Row3 = -worldMatrix.Row3; - - Matrix worldViewMatrix; - Matrix worldViewProjectionMatrix; - Matrix.Multiply(ref worldMatrix, ref camera.ViewMatrix, out worldViewMatrix); - Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out worldViewProjectionMatrix); - - return worldViewProjectionMatrix; - } - - private static Matrix GetWorldViewProjection(UIDocument uiDocument, Vector3 virtualResolution) + /// + /// Removes the specified from the . + /// + /// The document to remove. + /// + /// true if the document was successfully removed; otherwise false. + /// This method also returns false the document was not present. + /// + /// A render object needs to be removed from the for the UI to stop being rendered. + public bool RemoveDocument(UIDocument document) { - var nearPlane = virtualResolution.Z / 2; - var farPlane = nearPlane + virtualResolution.Z; - var zOffset = nearPlane + virtualResolution.Z / 2; - var aspectRatio = virtualResolution.X / virtualResolution.Y; - var verticalFov = MathF.Atan2(virtualResolution.Y / 2, zOffset) * 2; - - var cameraComponent = new CameraComponent(nearPlane, farPlane) - { - UseCustomAspectRatio = true, - AspectRatio = aspectRatio, - VerticalFieldOfView = MathUtil.RadiansToDegrees(verticalFov), - ViewMatrix = Matrix.LookAtRH(new Vector3(0, 0, zOffset), Vector3.Zero, Vector3.UnitY), - ProjectionMatrix = Matrix.PerspectiveFovRH(verticalFov, aspectRatio, nearPlane, farPlane), - }; - - return GetWorldViewProjection(uiDocument, cameraComponent); + return documents.Remove(document); } + /// /// Represents the result of a hit test on the UI. @@ -643,14 +538,4 @@ public HitTestResult(float depthBias, UIElement element, Vector3 intersection) public Vector3 IntersectionPoint { get; } } } - - public class UIElementInputState - { - - - public UIElementInputState() - { - - } - } } From 5869d3ed3fa537f2441a2d179a73244400f277fc Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Sun, 29 Sep 2024 15:25:08 -0700 Subject: [PATCH 11/20] Fixed UI hover state not change when pointer is outside of UI --- sources/engine/Stride.UI/UISystem.Picking.cs | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index dbc708ba12..1a1004a50d 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -35,6 +35,8 @@ private partial void Pick(GameTime gameTime) return; Texture renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; + // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). + Viewport viewport = renderContext.ViewportState.Viewport0; UIElement elementUnderPointer = null; // Prepare content required for Picking and MouseOver events @@ -42,11 +44,11 @@ private partial void Pick(GameTime gameTime) foreach (var cameraSlot in sceneSystem.GraphicsCompositor.Cameras) { - // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). - var viewport = renderContext.ViewportState.Viewport0; - foreach (var uiDocument in documents) { + if (!uiDocument.Enabled) + continue; + Matrix worldViewProjection = uiDocument.GetWorldViewProjection(cameraSlot.Camera, renderTarget); // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) @@ -54,7 +56,7 @@ private partial void Pick(GameTime gameTime) { UIElement renderPageElementUnderPointer = null; UpdateRenderPagePointerInput(uiDocument, viewport, ref worldViewProjection, gameTime, ref renderPageElementUnderPointer); - + // only update result element, when this one has a value if (renderPageElementUnderPointer != null) elementUnderPointer = renderPageElementUnderPointer; @@ -63,7 +65,7 @@ private partial void Pick(GameTime gameTime) } PickingClear(); - + UIElementUnderMouseCursor = elementUnderPointer; } @@ -133,7 +135,7 @@ private void ClearPointerEvents() /// /// The position of the lick on the screen in normalized (0..1, 0..1) range /// from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range - /// + /// true when the screen point of the ray would be within the bounds of the UI document; otherwise, false. private bool TryGetRenderPageRay(Vector3 resolution, ref Viewport viewport, ref Matrix worldViewProj, Vector2 screenPosition, out Ray uiRay) { uiRay = new Ray(new Vector3(float.NegativeInfinity), new Vector3(0, 1, 0)); @@ -291,12 +293,14 @@ private UIElement UpdateMouseOver(UIDocument uiDocument, ref Viewport viewport, if (mousePosition != uiDocument.LastMousePosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) { Ray uiRay; - if (!TryGetRenderPageRay(uiDocument.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) - return null; - mouseOverElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); + if (TryGetRenderPageRay(uiDocument.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) + mouseOverElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); + else + mouseOverElement = null; } + // find the common parent between current and last overred elements var commonElement = FindCommonParent(mouseOverElement, lastPointerOverElement); From 8707c0b95cb89ee2f9640ac70e8c90108e60fa70 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Sun, 29 Sep 2024 16:50:55 -0700 Subject: [PATCH 12/20] [Breaking Change] Replaced TouchEventArgs and refactored naming for consistency --- .../UIEditor/Adorners/SizingAdorner.cs | 4 +- .../Game/UIEditorGameAdornerService.Events.cs | 14 +- .../Game/UIEditorGameAdornerService.cs | 8 +- sources/engine/Stride.Input/PointerEvent.cs | 2 +- .../Stride.UI.Tests/Regression/ButtonTest.cs | 2 +- .../Regression/ImageButtonTest.cs | 2 +- .../Regression/MouseOverTest.cs | 80 +++--- sources/engine/Stride.UI/Controls/Button.cs | 2 +- .../engine/Stride.UI/Controls/ButtonBase.cs | 12 +- .../Controls/EditText.Direct.Default.cs | 2 +- .../Stride.UI/Controls/EditText.Direct.cs | 4 +- sources/engine/Stride.UI/Controls/EditText.cs | 24 +- .../engine/Stride.UI/Controls/ModalElement.cs | 4 +- .../engine/Stride.UI/Controls/ScrollViewer.cs | 42 ++-- sources/engine/Stride.UI/Controls/Slider.cs | 16 +- sources/engine/Stride.UI/MouseOverState.cs | 26 -- sources/engine/Stride.UI/Panels/Panel.cs | 4 +- sources/engine/Stride.UI/PointerEventArgs.cs | 72 ++++++ sources/engine/Stride.UI/PointerOverState.cs | 26 ++ .../Renderers/DefaultEditTextRenderer.cs | 4 +- .../Renderers/DefaultSliderRenderer.cs | 2 +- sources/engine/Stride.UI/TouchEventArgs.cs | 45 ---- sources/engine/Stride.UI/UIElement.Events.cs | 232 +++++++++--------- sources/engine/Stride.UI/UIElement.cs | 4 +- sources/engine/Stride.UI/UISystem.Picking.cs | 118 +++++---- 25 files changed, 383 insertions(+), 368 deletions(-) delete mode 100644 sources/engine/Stride.UI/MouseOverState.cs create mode 100644 sources/engine/Stride.UI/PointerEventArgs.cs create mode 100644 sources/engine/Stride.UI/PointerOverState.cs delete mode 100644 sources/engine/Stride.UI/TouchEventArgs.cs diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs index 214891c577..9c77f0ae17 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs @@ -147,12 +147,12 @@ void IResizingAdorner.OnResizingCompleted() isDragging = false; } - private void MouseOverStateChanged(object sender, PropertyChangedArgs e) + private void MouseOverStateChanged(object sender, PropertyChangedArgs e) { if (isDragging) return; - Service.Controller.ChangeCursor(e.NewValue != MouseOverState.MouseOverNone ? GetCursor() : null); + Service.Controller.ChangeCursor(e.NewValue != PointerOverState.None ? GetCursor() : null); } } } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs index d3dcf43796..d3b965b889 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.Events.cs @@ -71,14 +71,14 @@ internal void ApplyChanges(Guid elementId, IReadOnlyDictionary c } #region Event Handlers - private void PreviewTouchDown(object sender, TouchEventArgs e) + private void PreviewPointerPressed(object sender, PointerEventArgs e) { if (!Game.Input.IsMouseButtonDown(MouseButton.Left)) return; // Save the current pointer position originWorldPosition = currentPosition = e.WorldPosition; - originScreenPosition = e.ScreenPosition; + originScreenPosition = e.Position; // No prior selection (selection is done on TouchUp) if (selectedAdorners.Count == 0) @@ -115,13 +115,13 @@ orderby hit.IntersectionPoint.Z isInProgress = true; } - private void PreviewTouchMove(object sender, TouchEventArgs e) + private void PreviewPointerMove(object sender, PointerEventArgs e) { var worldPosition = e.WorldPosition; DoHighlightingAtPosition(ref worldPosition); } - private void TouchMove(object sender, TouchEventArgs e) + private void PointerMove(object sender, PointerEventArgs e) { // dragging state if (isInProgress) @@ -134,7 +134,7 @@ private void TouchMove(object sender, TouchEventArgs e) if (!isDragging) { // Start dragging only if a minimum distance is reached (on the real screen, not the virtual screen). - var delta = (e.ScreenPosition - originScreenPosition)*new Vector2(Game.GraphicsDevice.Presenter.BackBuffer.Width, Game.GraphicsDevice.Presenter.BackBuffer.Height); + var delta = (e.Position - originScreenPosition)*new Vector2(Game.GraphicsDevice.Presenter.BackBuffer.Width, Game.GraphicsDevice.Presenter.BackBuffer.Height); if (Math.Abs(delta.X) > System.Windows.SystemParameters.MinimumHorizontalDragDistance || Math.Abs(delta.Y) > System.Windows.SystemParameters.MinimumVerticalDragDistance) { isDragging = true; @@ -183,7 +183,7 @@ private void TouchMove(object sender, TouchEventArgs e) // TODO: special case when trying to move when there is nothing selected: should select and start moving at the same time } - private void TouchUp(object sender, TouchEventArgs e) + private void PointerReleased(object sender, PointerEventArgs e) { if (!Game.Input.IsMouseButtonReleased(MouseButton.Left)) return; @@ -231,7 +231,7 @@ private void TouchUp(object sender, TouchEventArgs e) } // Select the corresponding asset-side UIElement, if pointer did not move between down and up events - var delta = (e.ScreenPosition - originScreenPosition)*new Vector2(Game.GraphicsDevice.Presenter.BackBuffer.Width, Game.GraphicsDevice.Presenter.BackBuffer.Height); + var delta = (e.Position - originScreenPosition)*new Vector2(Game.GraphicsDevice.Presenter.BackBuffer.Width, Game.GraphicsDevice.Presenter.BackBuffer.Height); if (Math.Abs(delta.X) < System.Windows.SystemParameters.MinimumHorizontalDragDistance && Math.Abs(delta.Y) < System.Windows.SystemParameters.MinimumVerticalDragDistance) { var additiveSelection = Game.Input.IsKeyDown(Keys.LeftCtrl) || Game.Input.IsKeyDown(Keys.RightCtrl); diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.cs index 42cf215001..5aa32ec0fc 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Game/UIEditorGameAdornerService.cs @@ -524,10 +524,10 @@ private async Task Update() if (adornerCanvas == null) continue; - adornerCanvas.PreviewTouchDown += PreviewTouchDown; - adornerCanvas.PreviewTouchMove += PreviewTouchMove; - adornerCanvas.TouchMove += TouchMove; - adornerCanvas.TouchUp += TouchUp; + adornerCanvas.PreviewPointerPressed += PreviewPointerPressed; + adornerCanvas.PreviewPointerMove += PreviewPointerMove; + adornerCanvas.PointerMove += PointerMove; + adornerCanvas.PointerReleased += PointerReleased; break; } diff --git a/sources/engine/Stride.Input/PointerEvent.cs b/sources/engine/Stride.Input/PointerEvent.cs index c62b1cc790..fe31b84a15 100644 --- a/sources/engine/Stride.Input/PointerEvent.cs +++ b/sources/engine/Stride.Input/PointerEvent.cs @@ -26,7 +26,7 @@ public class PointerEvent : InputEvent public Vector2 AbsolutePosition => Position * Pointer.SurfaceSize; /// - /// Gets the normalized screen position of the pointer. + /// Gets the normalized screen position of the pointer. Position is normalized between [0,1]. (0,0) is the left top corner, (1,1) is the right bottom corner. /// /// The position. public Vector2 Position { get; internal set; } diff --git a/sources/engine/Stride.UI.Tests/Regression/ButtonTest.cs b/sources/engine/Stride.UI.Tests/Regression/ButtonTest.cs index 010bc53e18..66396ca63e 100644 --- a/sources/engine/Stride.UI.Tests/Regression/ButtonTest.cs +++ b/sources/engine/Stride.UI.Tests/Regression/ButtonTest.cs @@ -40,7 +40,7 @@ protected override void RegisterTests() private void DrawTest1() { - button.RaiseTouchDownEvent(new TouchEventArgs()); + button.RaisePointerPressedEvent(new TouchEventArgs()); } [Fact] diff --git a/sources/engine/Stride.UI.Tests/Regression/ImageButtonTest.cs b/sources/engine/Stride.UI.Tests/Regression/ImageButtonTest.cs index 4f9fb8367a..ec113ffede 100644 --- a/sources/engine/Stride.UI.Tests/Regression/ImageButtonTest.cs +++ b/sources/engine/Stride.UI.Tests/Regression/ImageButtonTest.cs @@ -46,7 +46,7 @@ protected override void RegisterTests() private void DrawTest1() { - button.RaiseTouchDownEvent(new TouchEventArgs()); + button.RaisePointerPressedEvent(new TouchEventArgs()); } [Fact] diff --git a/sources/engine/Stride.UI.Tests/Regression/MouseOverTest.cs b/sources/engine/Stride.UI.Tests/Regression/MouseOverTest.cs index 6283c2d86d..94425c61d8 100644 --- a/sources/engine/Stride.UI.Tests/Regression/MouseOverTest.cs +++ b/sources/engine/Stride.UI.Tests/Regression/MouseOverTest.cs @@ -33,8 +33,8 @@ public class MouseOverTest : UITestGameBase private bool triggeredCanvas; private bool triggeredStackPanel; - private MouseOverState oldValueButton1; - private MouseOverState newValueButton1; + private PointerOverState oldValueButton1; + private PointerOverState newValueButton1; public MouseOverTest() { @@ -119,12 +119,12 @@ private void Test1() { ResetStates(); - Assert.Equal(MouseOverState.MouseOverNone, canvas.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, stackPanel.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button2.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit2.MouseOverState); + Assert.Equal(PointerOverState.None, canvas.PointerOverState); + Assert.Equal(PointerOverState.None, stackPanel.PointerOverState); + Assert.Equal(PointerOverState.None, button1.PointerOverState); + Assert.Equal(PointerOverState.None, button2.PointerOverState); + Assert.Equal(PointerOverState.None, edit1.PointerOverState); + Assert.Equal(PointerOverState.None, edit2.PointerOverState); Assert.False(triggeredButton1); Assert.False(triggeredButton2); @@ -143,12 +143,12 @@ private void PrepareTest2() private void Test2() { - Assert.Equal(MouseOverState.MouseOverChild, canvas.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, stackPanel.MouseOverState); - Assert.Equal(MouseOverState.MouseOverElement, button1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button2.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit2.MouseOverState); + Assert.Equal(PointerOverState.Child, canvas.PointerOverState); + Assert.Equal(PointerOverState.None, stackPanel.PointerOverState); + Assert.Equal(PointerOverState.Self, button1.PointerOverState); + Assert.Equal(PointerOverState.None, button2.PointerOverState); + Assert.Equal(PointerOverState.None, edit1.PointerOverState); + Assert.Equal(PointerOverState.None, edit2.PointerOverState); Assert.True(triggeredButton1); Assert.True(triggeredCanvas); @@ -157,8 +157,8 @@ private void Test2() Assert.False(triggeredEdit2); Assert.False(triggeredStackPanel); - Assert.Equal(MouseOverState.MouseOverNone, oldValueButton1); - Assert.Equal(MouseOverState.MouseOverElement, newValueButton1); + Assert.Equal(PointerOverState.None, oldValueButton1); + Assert.Equal(PointerOverState.Self, newValueButton1); } private void PrepareTest3() @@ -170,12 +170,12 @@ private void PrepareTest3() private void Test3() { - Assert.Equal(MouseOverState.MouseOverChild, canvas.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, stackPanel.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button2.MouseOverState); - Assert.Equal(MouseOverState.MouseOverElement, edit1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit2.MouseOverState); + Assert.Equal(PointerOverState.Child, canvas.PointerOverState); + Assert.Equal(PointerOverState.None, stackPanel.PointerOverState); + Assert.Equal(PointerOverState.None, button1.PointerOverState); + Assert.Equal(PointerOverState.None, button2.PointerOverState); + Assert.Equal(PointerOverState.Self, edit1.PointerOverState); + Assert.Equal(PointerOverState.None, edit2.PointerOverState); Assert.True(triggeredButton1); Assert.True(triggeredEdit1); @@ -194,12 +194,12 @@ private void PrepareTest4() private void Test4() { - Assert.Equal(MouseOverState.MouseOverElement, canvas.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, stackPanel.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button2.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit2.MouseOverState); + Assert.Equal(PointerOverState.Self, canvas.PointerOverState); + Assert.Equal(PointerOverState.None, stackPanel.PointerOverState); + Assert.Equal(PointerOverState.None, button1.PointerOverState); + Assert.Equal(PointerOverState.None, button2.PointerOverState); + Assert.Equal(PointerOverState.None, edit1.PointerOverState); + Assert.Equal(PointerOverState.None, edit2.PointerOverState); Assert.True(triggeredEdit1); Assert.True(triggeredCanvas); @@ -218,12 +218,12 @@ private void PrepareTest5() private void Test5() { - Assert.Equal(MouseOverState.MouseOverChild, canvas.MouseOverState); - Assert.Equal(MouseOverState.MouseOverChild, stackPanel.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverElement, button2.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit2.MouseOverState); + Assert.Equal(PointerOverState.Child, canvas.PointerOverState); + Assert.Equal(PointerOverState.Child, stackPanel.PointerOverState); + Assert.Equal(PointerOverState.None, button1.PointerOverState); + Assert.Equal(PointerOverState.Self, button2.PointerOverState); + Assert.Equal(PointerOverState.None, edit1.PointerOverState); + Assert.Equal(PointerOverState.None, edit2.PointerOverState); Assert.True(triggeredCanvas); Assert.True(triggeredButton2); @@ -242,12 +242,12 @@ private void PrepareTest6() private void Test6() { - Assert.Equal(MouseOverState.MouseOverChild, canvas.MouseOverState); - Assert.Equal(MouseOverState.MouseOverChild, stackPanel.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, button2.MouseOverState); - Assert.Equal(MouseOverState.MouseOverNone, edit1.MouseOverState); - Assert.Equal(MouseOverState.MouseOverElement, edit2.MouseOverState); + Assert.Equal(PointerOverState.Child, canvas.PointerOverState); + Assert.Equal(PointerOverState.Child, stackPanel.PointerOverState); + Assert.Equal(PointerOverState.None, button1.PointerOverState); + Assert.Equal(PointerOverState.None, button2.PointerOverState); + Assert.Equal(PointerOverState.None, edit1.PointerOverState); + Assert.Equal(PointerOverState.Self, edit2.PointerOverState); Assert.True(triggeredEdit2); Assert.True(triggeredButton2); diff --git a/sources/engine/Stride.UI/Controls/Button.cs b/sources/engine/Stride.UI/Controls/Button.cs index c117f323be..ce864b2560 100644 --- a/sources/engine/Stride.UI/Controls/Button.cs +++ b/sources/engine/Stride.UI/Controls/Button.cs @@ -165,7 +165,7 @@ public bool SizeToContent } } - internal ISpriteProvider ButtonImageProvider => IsPressed ? PressedImage : (MouseOverState == MouseOverState.MouseOverElement && MouseOverImage != null ? MouseOverImage : NotPressedImage); + internal ISpriteProvider ButtonImageProvider => IsPressed ? PressedImage : (PointerOverState == PointerOverState.Self && MouseOverImage != null ? MouseOverImage : NotPressedImage); internal Sprite ButtonImage => ButtonImageProvider?.GetSprite(); diff --git a/sources/engine/Stride.UI/Controls/ButtonBase.cs b/sources/engine/Stride.UI/Controls/ButtonBase.cs index fff424aee4..ac3cd3d3f2 100644 --- a/sources/engine/Stride.UI/Controls/ButtonBase.cs +++ b/sources/engine/Stride.UI/Controls/ButtonBase.cs @@ -65,9 +65,9 @@ public event EventHandler Click typeof(Button)); - protected override void OnTouchDown(TouchEventArgs args) + protected override void OnPointerPressed(PointerEventArgs args) { - base.OnTouchDown(args); + base.OnPointerPressed(args); IsPressed = true; @@ -75,9 +75,9 @@ protected override void OnTouchDown(TouchEventArgs args) RaiseEvent(new RoutedEventArgs(ClickEvent)); } - protected override void OnTouchUp(TouchEventArgs args) + protected override void OnPointerReleased(PointerEventArgs args) { - base.OnTouchUp(args); + base.OnPointerReleased(args); if (IsPressed && ClickMode == ClickMode.Release) RaiseEvent(new RoutedEventArgs(ClickEvent)); @@ -85,9 +85,9 @@ protected override void OnTouchUp(TouchEventArgs args) IsPressed = false; } - protected override void OnTouchLeave(TouchEventArgs args) + protected override void OnPointerLeave(PointerEventArgs args) { - base.OnTouchLeave(args); + base.OnPointerLeave(args); IsPressed = false; } diff --git a/sources/engine/Stride.UI/Controls/EditText.Direct.Default.cs b/sources/engine/Stride.UI/Controls/EditText.Direct.Default.cs index 9b919db9b5..8db57e266d 100644 --- a/sources/engine/Stride.UI/Controls/EditText.Direct.Default.cs +++ b/sources/engine/Stride.UI/Controls/EditText.Direct.Default.cs @@ -47,7 +47,7 @@ private void UpdateSelectionToEditImpl() { } - private void OnTouchUpImpl(TouchEventArgs args) + private void OnPointerReleasedImpl(PointerEventArgs args) { } } diff --git a/sources/engine/Stride.UI/Controls/EditText.Direct.cs b/sources/engine/Stride.UI/Controls/EditText.Direct.cs index cf7ece964d..e0ea1b95e7 100644 --- a/sources/engine/Stride.UI/Controls/EditText.Direct.cs +++ b/sources/engine/Stride.UI/Controls/EditText.Direct.cs @@ -13,7 +13,7 @@ namespace Stride.UI.Controls { public partial class EditText { - private void OnTouchMoveImpl(TouchEventArgs args) + private void OnPointerMoveImpl(PointerEventArgs args) { var currentPosition = FindNearestCharacterIndex(new Vector2(args.WorldPosition.X - WorldMatrix.M41, args.WorldPosition.Y - WorldMatrix.M42)); @@ -33,7 +33,7 @@ private void OnTouchMoveImpl(TouchEventArgs args) } } - private void OnTouchDownImpl(TouchEventArgs args) + private void OnPointerPressedImpl(PointerEventArgs args) { // Find the appropriate position for the caret. CaretPosition = FindNearestCharacterIndex(new Vector2(args.WorldPosition.X - WorldMatrix.M41, args.WorldPosition.Y - WorldMatrix.M42)); diff --git a/sources/engine/Stride.UI/Controls/EditText.cs b/sources/engine/Stride.UI/Controls/EditText.cs index 52ccb524b6..23f2b774a6 100644 --- a/sources/engine/Stride.UI/Controls/EditText.cs +++ b/sources/engine/Stride.UI/Controls/EditText.cs @@ -859,42 +859,42 @@ protected virtual void OnTextChanged(RoutedEventArgs args) { } - protected override void OnTouchDown(TouchEventArgs args) + protected override void OnPointerPressed(PointerEventArgs args) { - base.OnTouchDown(args); + base.OnPointerPressed(args); IsSelectionActive = !IsReadOnly; IsTouchedDown = true; - OnTouchDownImpl(args); + OnPointerPressedImpl(args); } - protected override void OnTouchUp(TouchEventArgs args) + protected override void OnPointerReleased(PointerEventArgs args) { - base.OnTouchUp(args); + base.OnPointerReleased(args); if (IsTouchedDown) { - OnTouchUpImpl(args); + OnPointerReleasedImpl(args); } IsTouchedDown = false; } - protected override void OnTouchLeave(TouchEventArgs args) + protected override void OnPointerLeave(PointerEventArgs args) { - base.OnTouchLeave(args); + base.OnPointerLeave(args); IsTouchedDown = false; } - protected override void OnTouchMove(TouchEventArgs args) + protected override void OnPointerMove(PointerEventArgs args) { - base.OnTouchMove(args); + base.OnPointerMove(args); if (IsTouchedDown) { - OnTouchMoveImpl(args); + OnPointerMoveImpl(args); } } } -} \ No newline at end of file +} diff --git a/sources/engine/Stride.UI/Controls/ModalElement.cs b/sources/engine/Stride.UI/Controls/ModalElement.cs index a833531873..20f0664072 100644 --- a/sources/engine/Stride.UI/Controls/ModalElement.cs +++ b/sources/engine/Stride.UI/Controls/ModalElement.cs @@ -69,9 +69,9 @@ public Color OverlayColor [DefaultValue(true)] public bool IsModal { get; set; } = true; - protected override void OnTouchUp(TouchEventArgs args) + protected override void OnPointerReleased(PointerEventArgs args) { - base.OnTouchUp(args); + base.OnPointerReleased(args); if (!IsModal || args.Source != this) return; diff --git a/sources/engine/Stride.UI/Controls/ScrollViewer.cs b/sources/engine/Stride.UI/Controls/ScrollViewer.cs index 2ec3208930..c6c92040fe 100644 --- a/sources/engine/Stride.UI/Controls/ScrollViewer.cs +++ b/sources/engine/Stride.UI/Controls/ScrollViewer.cs @@ -785,54 +785,54 @@ protected override void UpdateWorldMatrix(ref Matrix parentWorldMatrix, bool par } } - protected override void OnPreviewTouchDown(TouchEventArgs args) + protected override void OnPreviewPointerPressed(PointerEventArgs args) { - base.OnPreviewTouchDown(args); + base.OnPreviewPointerPressed(args); StopCurrentScrolling(); accumulatedTranslation = Vector3.Zero; IsTouchedDown = true; } - protected override void OnPreviewTouchUp(TouchEventArgs args) + protected override void OnPreviewPointerReleased(PointerEventArgs args) { - base.OnPreviewTouchUp(args); + base.OnPreviewPointerReleased(args); if (IsUserScrollingViewer) { args.Handled = true; - RaiseLeaveTouchEventToHierarchyChildren(this, args); + RaiseLeavePointerEventToHierarchyChildren(this, args); } IsUserScrollingViewer = false; IsTouchedDown = false; } - protected override void OnTouchEnter(TouchEventArgs args) + protected override void OnPointerEnter(PointerEventArgs args) { - base.OnTouchEnter(args); + base.OnPointerEnter(args); StopCurrentScrolling(); accumulatedTranslation = Vector3.Zero; } - protected override void OnTouchLeave(TouchEventArgs args) + protected override void OnPointerLeave(PointerEventArgs args) { - base.OnTouchLeave(args); + base.OnPointerLeave(args); IsUserScrollingViewer = false; - IsTouchedDown = false; + IsPointerDown = false; } - protected override void OnPreviewTouchMove(TouchEventArgs args) + protected override void OnPreviewPointerMove(PointerEventArgs args) { - base.OnPreviewTouchMove(args); + base.OnPreviewPointerMove(args); if (ScrollMode == ScrollingMode.None || !TouchScrollingEnabled || !IsTouchedDown) return; // accumulate all the touch moves of the frame - var translation = args.WorldTranslation; + var translation = args.WorldDeltaPosition; foreach (var index in ScrollModeToDirectionIndices[ScrollMode]) lastFrameTranslation[index] -= translation[index]; @@ -847,25 +847,19 @@ protected override void OnPreviewTouchMove(TouchEventArgs args) args.Handled = true; } - private static void RaiseLeaveTouchEventToHierarchyChildren(UIElement parent, TouchEventArgs args) + private static void RaiseLeavePointerEventToHierarchyChildren(UIElement parent, PointerEventArgs args) { if (parent == null) return; - var argsCopy = new TouchEventArgs - { - Action = args.Action, - ScreenPosition = args.ScreenPosition, - ScreenTranslation = args.ScreenTranslation, - Timestamp = args.Timestamp - }; + var argsCopy = args.Clone(); foreach (var child in parent.VisualChildrenCollection) { - if (child.IsTouched) + if (child.IsPointerDown) { - child.RaiseTouchLeaveEvent(argsCopy); - RaiseLeaveTouchEventToHierarchyChildren(child, args); + child.RaisePointerLeaveEvent(argsCopy); + RaiseLeavePointerEventToHierarchyChildren(child, args); } } } diff --git a/sources/engine/Stride.UI/Controls/Slider.cs b/sources/engine/Stride.UI/Controls/Slider.cs index 6823d706dd..7f92dd120b 100644 --- a/sources/engine/Stride.UI/Controls/Slider.cs +++ b/sources/engine/Stride.UI/Controls/Slider.cs @@ -388,31 +388,31 @@ private static void ValueChangedClassHandler(object sender, RoutedEventArgs args slider.OnValueChanged(args); } - protected override void OnTouchDown(TouchEventArgs args) + protected override void OnPointerPressed(PointerEventArgs args) { - base.OnTouchDown(args); + base.OnPointerPressed(args); SetValueFromTouchPosition(args.WorldPosition); IsTouchedDown = true; } - protected override void OnTouchUp(TouchEventArgs args) + protected override void OnPointerReleased(PointerEventArgs args) { - base.OnTouchUp(args); + base.OnPointerReleased(args); IsTouchedDown = false; } - protected override void OnTouchLeave(TouchEventArgs args) + protected override void OnPointerLeave(PointerEventArgs args) { - base.OnTouchLeave(args); + base.OnPointerLeave(args); IsTouchedDown = false; } - protected override void OnTouchMove(TouchEventArgs args) + protected override void OnPointerMove(PointerEventArgs args) { - base.OnTouchMove(args); + base.OnPointerMove(args); if (IsTouchedDown) { diff --git a/sources/engine/Stride.UI/MouseOverState.cs b/sources/engine/Stride.UI/MouseOverState.cs deleted file mode 100644 index b527c72799..0000000000 --- a/sources/engine/Stride.UI/MouseOverState.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.UI -{ - /// - /// Describe the possible states of the mouse over an UI element. - /// - public enum MouseOverState - { - /// - /// The mouse is neither over the element nor one of its children. - /// - /// The mouse is neither over the element nor one of its children. - MouseOverNone, - /// - /// The mouse is over one of children of the element. - /// - /// The mouse is over one of children of the element. - MouseOverChild, - /// - /// The mouse is directly over the element. - /// - /// The mouse is directly over the element. - MouseOverElement, - } -} diff --git a/sources/engine/Stride.UI/Panels/Panel.cs b/sources/engine/Stride.UI/Panels/Panel.cs index 50ff46fc34..76a66157b5 100644 --- a/sources/engine/Stride.UI/Panels/Panel.cs +++ b/sources/engine/Stride.UI/Panels/Panel.cs @@ -144,8 +144,8 @@ protected virtual void OnLogicalChildRemoved(UIElement oldElement, int index) SetParent(oldElement, null); SetVisualParent(oldElement, null); - if (oldElement.MouseOverState != MouseOverState.MouseOverNone) - MouseOverState = MouseOverState.MouseOverNone; + if (oldElement.PointerOverState != PointerOverState.None) + PointerOverState = PointerOverState.None; } /// diff --git a/sources/engine/Stride.UI/PointerEventArgs.cs b/sources/engine/Stride.UI/PointerEventArgs.cs new file mode 100644 index 0000000000..e131c93b0d --- /dev/null +++ b/sources/engine/Stride.UI/PointerEventArgs.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using Stride.Core.Mathematics; +using Stride.Input; +using Stride.UI.Events; + +namespace Stride.UI; + +public class PointerEventArgs : RoutedEventArgs +{ + // Pointer properties... + + /// + public IInputDevice Device { get; protected internal set; } + + /// + public int PointerId { get; internal set; } + + /// + public Vector2 AbsolutePosition => Position * Pointer.SurfaceSize; + + /// + public Vector2 Position { get; internal set; } + + /// + public Vector2 AbsoluteDeltaPosition => DeltaPosition * Pointer.SurfaceSize; + + /// + public Vector2 DeltaPosition { get; internal set; } + + /// + public TimeSpan DeltaTime { get; internal set; } + + /// + public PointerEventType EventType { get; internal set; } + + /// + public bool IsDown { get; internal set; } + + /// + public IPointerDevice Pointer => (IPointerDevice)Device; + + // UI specific properties... + + /// + /// Gets the position of the touch in the UI virtual world space. + /// + public Vector3 WorldPosition { get; internal set; } + + /// + /// Gets the translation of the touch in the UI virtual world space. + /// + public Vector3 WorldDeltaPosition { get; internal set; } + + public PointerEventArgs Clone() + { + return new PointerEventArgs() + { + Device = Device, + PointerId = PointerId, + Position = Position, + DeltaPosition = DeltaPosition, + DeltaTime = DeltaTime, + EventType = EventType, + IsDown = IsDown, + WorldPosition = WorldPosition, + WorldDeltaPosition = WorldDeltaPosition + }; + } +} diff --git a/sources/engine/Stride.UI/PointerOverState.cs b/sources/engine/Stride.UI/PointerOverState.cs new file mode 100644 index 0000000000..db668fb37f --- /dev/null +++ b/sources/engine/Stride.UI/PointerOverState.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +namespace Stride.UI +{ + /// + /// Describe the possible states of the pointer over an . + /// + public enum PointerOverState + { + /// + /// The pointer is neither over the element nor one of its children. + /// + /// The pointer is neither over the element nor one of its children. + None, + /// + /// The pointer is over one of children of the element. + /// + /// The pointer is over one of children of the element. + Child, + /// + /// The pointer is directly over the element. + /// + /// The pointer is directly over the element. + Self, + } +} diff --git a/sources/engine/Stride.UI/Renderers/DefaultEditTextRenderer.cs b/sources/engine/Stride.UI/Renderers/DefaultEditTextRenderer.cs index 9009acbeed..a508571e71 100644 --- a/sources/engine/Stride.UI/Renderers/DefaultEditTextRenderer.cs +++ b/sources/engine/Stride.UI/Renderers/DefaultEditTextRenderer.cs @@ -30,7 +30,7 @@ private void RenderSelection(EditText editText, UIRenderingContext context, int // determine the image to draw in background of the edit text var fontScale = editText.LayoutingContext.RealVirtualResolutionRatio; - var provider = editText.IsSelectionActive ? editText.ActiveImage : editText.MouseOverState == MouseOverState.MouseOverElement ? editText.MouseOverImage : editText.InactiveImage; + var provider = editText.IsSelectionActive ? editText.ActiveImage : editText.PointerOverState == PointerOverState.Self ? editText.MouseOverImage : editText.InactiveImage; var image = provider?.GetSprite(); var fontSize = new Vector2(fontScale.Y * editText.ActualTextSize); @@ -82,7 +82,7 @@ public override void RenderColor(UIElement element, UIRenderingContext context) // determine the image to draw in background of the edit text var fontScale = element.LayoutingContext.RealVirtualResolutionRatio; var color = editText.RenderOpacity * Color.White; - var provider = editText.IsSelectionActive ? editText.ActiveImage : editText.MouseOverState == MouseOverState.MouseOverElement ? editText.MouseOverImage : editText.InactiveImage; + var provider = editText.IsSelectionActive ? editText.ActiveImage : editText.PointerOverState == PointerOverState.Self ? editText.MouseOverImage : editText.InactiveImage; var image = provider?.GetSprite(); if (image?.Texture != null) diff --git a/sources/engine/Stride.UI/Renderers/DefaultSliderRenderer.cs b/sources/engine/Stride.UI/Renderers/DefaultSliderRenderer.cs index ac86b8c7f6..52203e713f 100644 --- a/sources/engine/Stride.UI/Renderers/DefaultSliderRenderer.cs +++ b/sources/engine/Stride.UI/Renderers/DefaultSliderRenderer.cs @@ -129,7 +129,7 @@ public override void RenderColor(UIElement element, UIRenderingContext context) } //draws the thumb - image = (slider.MouseOverState == MouseOverState.MouseOverElement ? slider.MouseOverThumbImage : slider.ThumbImage)?.GetSprite(); + image = (slider.PointerOverState == PointerOverState.Self ? slider.MouseOverThumbImage : slider.ThumbImage)?.GetSprite(); if (image?.Texture != null) { var imageAxis = (int)image.Orientation; diff --git a/sources/engine/Stride.UI/TouchEventArgs.cs b/sources/engine/Stride.UI/TouchEventArgs.cs deleted file mode 100644 index 374c8b5e30..0000000000 --- a/sources/engine/Stride.UI/TouchEventArgs.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; - -using Stride.Core.Mathematics; -using Stride.UI.Events; - -namespace Stride.UI -{ - /// - /// Provides data for touch input events. - /// - public class TouchEventArgs : RoutedEventArgs - { - /// - /// Gets the time when this event occurred. - /// - public TimeSpan Timestamp { get; internal set; } - - /// - /// Gets the action that occurred. - /// - public TouchAction Action { get; internal set; } - - /// - /// Gets the position of the touch on the screen. Position is normalized between [0,1]. (0,0) is the left top corner, (1,1) is the right bottom corner. - /// - public Vector2 ScreenPosition { get; internal set; } - - /// - /// Gets the translation of the touch on the screen since last triggered event (in normalized units). (1,1) represent a translation of the top left corner to the bottom right corner. - /// - public Vector2 ScreenTranslation { get; internal set; } - - /// - /// Gets the position of the touch in the UI virtual world space. - /// - public Vector3 WorldPosition { get; internal set; } - - /// - /// Gets the translation of the touch in the UI virtual world space. - /// - public Vector3 WorldTranslation { get; internal set; } - } -} diff --git a/sources/engine/Stride.UI/UIElement.Events.cs b/sources/engine/Stride.UI/UIElement.Events.cs index 9d4718a483..7a5a2e8f11 100644 --- a/sources/engine/Stride.UI/UIElement.Events.cs +++ b/sources/engine/Stride.UI/UIElement.Events.cs @@ -14,29 +14,29 @@ public abstract partial class UIElement { #region Routed Events - private static readonly RoutedEvent PreviewTouchDownEvent = - EventManager.RegisterRoutedEvent("PreviewTouchDown", RoutingStrategy.Tunnel, typeof(UIElement)); + private static readonly RoutedEvent PreviewPointerPressedEvent = + EventManager.RegisterRoutedEvent("PreviewPointerPressed", RoutingStrategy.Tunnel, typeof(UIElement)); - private static readonly RoutedEvent PreviewTouchMoveEvent = - EventManager.RegisterRoutedEvent("PreviewTouchMove", RoutingStrategy.Tunnel, typeof(UIElement)); + private static readonly RoutedEvent PreviewPointerMoveEvent = + EventManager.RegisterRoutedEvent("PreviewPointerMove", RoutingStrategy.Tunnel, typeof(UIElement)); - private static readonly RoutedEvent PreviewTouchUpEvent = - EventManager.RegisterRoutedEvent("PreviewTouchUp", RoutingStrategy.Tunnel, typeof(UIElement)); + private static readonly RoutedEvent PreviewPointerReleasedEvent = + EventManager.RegisterRoutedEvent("PreviewPointerReleased", RoutingStrategy.Tunnel, typeof(UIElement)); - private static readonly RoutedEvent TouchDownEvent = - EventManager.RegisterRoutedEvent("TouchDown", RoutingStrategy.Bubble, typeof(UIElement)); + private static readonly RoutedEvent PointerPressedEvent = + EventManager.RegisterRoutedEvent("PointerPressed", RoutingStrategy.Bubble, typeof(UIElement)); - private static readonly RoutedEvent TouchEnterEvent = - EventManager.RegisterRoutedEvent("TouchEnter", RoutingStrategy.Direct, typeof(UIElement)); + private static readonly RoutedEvent PointerEnterEvent = + EventManager.RegisterRoutedEvent("PointerEnter", RoutingStrategy.Direct, typeof(UIElement)); - private static readonly RoutedEvent TouchLeaveEvent = - EventManager.RegisterRoutedEvent("TouchLeave", RoutingStrategy.Direct, typeof(UIElement)); + private static readonly RoutedEvent PointerLeaveEvent = + EventManager.RegisterRoutedEvent("PointerLeave", RoutingStrategy.Direct, typeof(UIElement)); - private static readonly RoutedEvent TouchMoveEvent = - EventManager.RegisterRoutedEvent("TouchMove", RoutingStrategy.Bubble, typeof(UIElement)); + private static readonly RoutedEvent PointerMoveEvent = + EventManager.RegisterRoutedEvent("PointerMove", RoutingStrategy.Bubble, typeof(UIElement)); - private static readonly RoutedEvent TouchUpEvent = - EventManager.RegisterRoutedEvent("TouchUp", RoutingStrategy.Bubble, typeof(UIElement)); + private static readonly RoutedEvent PointerReleaseEvent = + EventManager.RegisterRoutedEvent("PointerReleased", RoutingStrategy.Bubble, typeof(UIElement)); private static readonly RoutedEvent KeyPressedEvent = EventManager.RegisterRoutedEvent("KeyPressed", RoutingStrategy.Bubble, typeof(UIElement)); @@ -57,14 +57,14 @@ public abstract partial class UIElement static UIElement() { // register the class handlers - EventManager.RegisterClassHandler(typeof(UIElement), PreviewTouchDownEvent, PreviewTouchDownClassHandler); - EventManager.RegisterClassHandler(typeof(UIElement), PreviewTouchMoveEvent, PreviewTouchMoveClassHandler); - EventManager.RegisterClassHandler(typeof(UIElement), PreviewTouchUpEvent, PreviewTouchUpClassHandler); - EventManager.RegisterClassHandler(typeof(UIElement), TouchDownEvent, TouchDownClassHandler); - EventManager.RegisterClassHandler(typeof(UIElement), TouchEnterEvent, TouchEnterClassHandler); - EventManager.RegisterClassHandler(typeof(UIElement), TouchLeaveEvent, TouchLeaveClassHandler); - EventManager.RegisterClassHandler(typeof(UIElement), TouchMoveEvent, TouchMoveClassHandler); - EventManager.RegisterClassHandler(typeof(UIElement), TouchUpEvent, TouchUpClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PreviewPointerPressedEvent, PreviewPointerPressedClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PreviewPointerMoveEvent, PreviewPointerMoveClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PreviewPointerReleasedEvent, PreviewPointerReleasedClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PointerPressedEvent, PointerPressedClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PointerEnterEvent, PointerEnterClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PointerLeaveEvent, PointerLeaveClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PointerMoveEvent, PointerMoveClassHandler); + EventManager.RegisterClassHandler(typeof(UIElement), PointerReleaseEvent, PointerReleasedClassHandler); EventManager.RegisterClassHandler(typeof(UIElement), KeyPressedEvent, KeyPressedClassHandler); EventManager.RegisterClassHandler(typeof(UIElement), KeyDownEvent, KeyDownClassHandler); EventManager.RegisterClassHandler(typeof(UIElement), KeyReleasedEvent, KeyReleasedClassHandler); @@ -75,26 +75,26 @@ static UIElement() /// Gets a value indicating whether the is currently touched by the user. /// [DataMemberIgnore] - public bool IsTouched { get; internal set; } + public bool IsPointerDown { get; internal set; } /// /// Gets the current state of the mouse over the UI element. /// - /// Only elements that can be clicked by user can have the MouseOverState.MouseOverElement value. + /// Only elements that can be clicked by user can have the PointerOverState.Self value. /// That is element that have set to true [DataMemberIgnore] - public MouseOverState MouseOverState + public PointerOverState PointerOverState { - get { return mouseOverState; } + get { return pointerOverState; } internal set { - var oldValue = mouseOverState; + var oldValue = pointerOverState; if (oldValue == value) return; - mouseOverState = value; + pointerOverState = value; - MouseOverStateChanged?.Invoke(this, new PropertyChangedArgs { NewValue = value, OldValue = oldValue }); + MouseOverStateChanged?.Invoke(this, new PropertyChangedArgs { NewValue = value, OldValue = oldValue }); } } @@ -102,10 +102,10 @@ internal set /// Gets or sets whether this element requires a mouse over check. /// /// - /// By default, the engine does not check whether + /// By default, the engine does not check whether /// of the element is changed while the cursor is still. This behavior is /// overriden when this parameter is set to true, which forces the engine to - /// check for changes of . + /// check for changes of . /// The engine sets this to true when the layout of the element changes. /// [DataMemberIgnore] @@ -227,19 +227,19 @@ public void RemoveHandler(RoutedEvent routedEvent, EventHandler handler #region Events /// - /// Occurs when the value of the property changed. + /// Occurs when the value of the property changed. /// /// This event is not a routed event - public event PropertyChangedHandler MouseOverStateChanged; + public event PropertyChangedHandler MouseOverStateChanged; /// /// Occurs when the user starts touching the . That is when he moves its finger down from the element. /// /// A click event is tunneling - public event EventHandler PreviewTouchDown + public event EventHandler PreviewPointerPressed { - add { AddHandler(PreviewTouchDownEvent, value); } - remove { RemoveHandler(PreviewTouchDownEvent, value); } + add { AddHandler(PreviewPointerPressedEvent, value); } + remove { RemoveHandler(PreviewPointerPressedEvent, value); } } /// @@ -247,30 +247,30 @@ public event EventHandler PreviewTouchDown /// That is when his finger was already on the element and moved from its previous position. /// /// A click event is tunneling - public event EventHandler PreviewTouchMove + public event EventHandler PreviewPointerMove { - add { AddHandler(PreviewTouchMoveEvent, value); } - remove { RemoveHandler(PreviewTouchMoveEvent, value); } + add { AddHandler(PreviewPointerMoveEvent, value); } + remove { RemoveHandler(PreviewPointerMoveEvent, value); } } /// /// Occurs when the user stops touching the . That is when he moves its finger up from the element. /// /// A click event is tunneling - public event EventHandler PreviewTouchUp + public event EventHandler PreviewPointerUp { - add { AddHandler(PreviewTouchUpEvent, value); } - remove { RemoveHandler(PreviewTouchUpEvent, value); } + add { AddHandler(PreviewPointerReleasedEvent, value); } + remove { RemoveHandler(PreviewPointerReleasedEvent, value); } } /// /// Occurs when the user starts touching the . That is when he moves its finger down from the element. /// /// A click event is bubbling - public event EventHandler TouchDown + public event EventHandler PointerDown { - add { AddHandler(TouchDownEvent, value); } - remove { RemoveHandler(TouchDownEvent, value); } + add { AddHandler(PointerPressedEvent, value); } + remove { RemoveHandler(PointerPressedEvent, value); } } /// @@ -278,10 +278,10 @@ public event EventHandler TouchDown /// That is when his finger was on the screen outside of the element and moved inside the element. /// /// A click event is bubbling - public event EventHandler TouchEnter + public event EventHandler PointerEnter { - add { AddHandler(TouchEnterEvent, value); } - remove { RemoveHandler(TouchEnterEvent, value); } + add { AddHandler(PointerEnterEvent, value); } + remove { RemoveHandler(PointerEnterEvent, value); } } /// @@ -289,10 +289,10 @@ public event EventHandler TouchEnter /// That is when his finger was inside of the element and moved on the screen outside of the element. /// /// A click event is bubbling - public event EventHandler TouchLeave + public event EventHandler PointerLeave { - add { AddHandler(TouchLeaveEvent, value); } - remove { RemoveHandler(TouchLeaveEvent, value); } + add { AddHandler(PointerLeaveEvent, value); } + remove { RemoveHandler(PointerLeaveEvent, value); } } /// @@ -300,20 +300,20 @@ public event EventHandler TouchLeave /// That is when his finger was already on the element and moved from its previous position. /// /// A click event is bubbling - public event EventHandler TouchMove + public event EventHandler PointerMove { - add { AddHandler(TouchMoveEvent, value); } - remove { RemoveHandler(TouchMoveEvent, value); } + add { AddHandler(PointerMoveEvent, value); } + remove { RemoveHandler(PointerMoveEvent, value); } } /// /// Occurs when the user stops touching the . That is when he moves its finger up from the element. /// /// A click event is bubbling - public event EventHandler TouchUp + public event EventHandler PointerReleased { - add { AddHandler(TouchUpEvent, value); } - remove { RemoveHandler(TouchUpEvent, value); } + add { AddHandler(PointerReleaseEvent, value); } + remove { RemoveHandler(PointerReleaseEvent, value); } } /// @@ -359,43 +359,43 @@ internal event EventHandler Input #region Internal Event Raiser - internal void RaiseTouchDownEvent(TouchEventArgs touchArgs) + internal void RaisePointerPressedEvent(PointerEventArgs pointerArgs) { - touchArgs.RoutedEvent = PreviewTouchDownEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PreviewPointerPressedEvent; + RaiseEvent(pointerArgs); - touchArgs.RoutedEvent = TouchDownEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PointerPressedEvent; + RaiseEvent(pointerArgs); } - internal void RaiseTouchEnterEvent(TouchEventArgs touchArgs) + internal void RaisePointerEnterEvent(PointerEventArgs pointerArgs) { - touchArgs.RoutedEvent = TouchEnterEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PointerEnterEvent; + RaiseEvent(pointerArgs); } - internal void RaiseTouchLeaveEvent(TouchEventArgs touchArgs) + internal void RaisePointerLeaveEvent(PointerEventArgs pointerArgs) { - touchArgs.RoutedEvent = TouchLeaveEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PointerLeaveEvent; + RaiseEvent(pointerArgs); } - internal void RaiseTouchMoveEvent(TouchEventArgs touchArgs) + internal void RaisePointerMoveEvent(PointerEventArgs pointerArgs) { - touchArgs.RoutedEvent = PreviewTouchMoveEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PreviewPointerMoveEvent; + RaiseEvent(pointerArgs); - touchArgs.RoutedEvent = TouchMoveEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PointerMoveEvent; + RaiseEvent(pointerArgs); } - internal void RaiseTouchUpEvent(TouchEventArgs touchArgs) + internal void RaisePointerReleasedEvent(PointerEventArgs pointerArgs) { - touchArgs.RoutedEvent = PreviewTouchUpEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PreviewPointerReleasedEvent; + RaiseEvent(pointerArgs); - touchArgs.RoutedEvent = TouchUpEvent; - RaiseEvent(touchArgs); + pointerArgs.RoutedEvent = PointerReleaseEvent; + RaiseEvent(pointerArgs); } internal void RaiseKeyPressedEvent(KeyEventArgs keyEventArgs) @@ -426,135 +426,135 @@ internal void RaiseTextInputEvent(TextEventArgs textInputEventArgs) #region Class Event Handlers - private static void PreviewTouchDownClassHandler(object sender, TouchEventArgs args) + private static void PreviewPointerPressedClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnPreviewTouchDown(args); + uiElementSender.OnPreviewPointerPressed(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnPreviewTouchDown(TouchEventArgs args) + protected virtual void OnPreviewPointerPressed(PointerEventArgs args) { - IsTouched = true; + IsPointerDown = true; } - private static void PreviewTouchMoveClassHandler(object sender, TouchEventArgs args) + private static void PreviewPointerMoveClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnPreviewTouchMove(args); + uiElementSender.OnPreviewPointerMove(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnPreviewTouchMove(TouchEventArgs args) + protected virtual void OnPreviewPointerMove(PointerEventArgs args) { } - private static void PreviewTouchUpClassHandler(object sender, TouchEventArgs args) + private static void PreviewPointerReleasedClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnPreviewTouchUp(args); + uiElementSender.OnPreviewPointerReleased(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnPreviewTouchUp(TouchEventArgs args) + protected virtual void OnPreviewPointerReleased(PointerEventArgs args) { - IsTouched = false; + IsPointerDown = false; } - private static void TouchDownClassHandler(object sender, TouchEventArgs args) + private static void PointerPressedClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnTouchDown(args); + uiElementSender.OnPointerPressed(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnTouchDown(TouchEventArgs args) + protected virtual void OnPointerPressed(PointerEventArgs args) { } - private static void TouchEnterClassHandler(object sender, TouchEventArgs args) + private static void PointerEnterClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnTouchEnter(args); + uiElementSender.OnPointerEnter(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnTouchEnter(TouchEventArgs args) + protected virtual void OnPointerEnter(PointerEventArgs args) { - IsTouched = true; + IsPointerDown = true; } - private static void TouchLeaveClassHandler(object sender, TouchEventArgs args) + private static void PointerLeaveClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnTouchLeave(args); + uiElementSender.OnPointerLeave(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnTouchLeave(TouchEventArgs args) + protected virtual void OnPointerLeave(PointerEventArgs args) { - IsTouched = false; + IsPointerDown = false; } - private static void TouchMoveClassHandler(object sender, TouchEventArgs args) + private static void PointerMoveClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnTouchMove(args); + uiElementSender.OnPointerMove(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnTouchMove(TouchEventArgs args) + protected virtual void OnPointerMove(PointerEventArgs args) { } - private static void TouchUpClassHandler(object sender, TouchEventArgs args) + private static void PointerReleasedClassHandler(object sender, PointerEventArgs args) { var uiElementSender = (UIElement)sender; if (uiElementSender.IsHierarchyEnabled) - uiElementSender.OnTouchUp(args); + uiElementSender.OnPointerReleased(args); } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event - protected virtual void OnTouchUp(TouchEventArgs args) + protected virtual void OnPointerReleased(PointerEventArgs args) { } diff --git a/sources/engine/Stride.UI/UIElement.cs b/sources/engine/Stride.UI/UIElement.cs index 8986d93d45..067e6a94ed 100644 --- a/sources/engine/Stride.UI/UIElement.cs +++ b/sources/engine/Stride.UI/UIElement.cs @@ -60,7 +60,7 @@ public abstract partial class UIElement : IUIElementUpdate, IUIElementChildren, private float minimumHeight; private float minimumDepth; private Matrix localMatrix = Matrix.Identity; - private MouseOverState mouseOverState; + private PointerOverState pointerOverState; private LayoutingContext layoutingContext; protected bool ArrangeChanged; @@ -203,7 +203,7 @@ public virtual bool IsEnabled { isEnabled = value; - MouseOverState = MouseOverState.MouseOverNone; + PointerOverState = PointerOverState.None; } } diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index 1a1004a50d..bdb0d427b7 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -27,7 +27,7 @@ public partial class UISystem /// Only elements with CanBeHitByUser == true are taken into account. /// Last processed element_state / ?UIComponent? with a valid element will be used. /// - public UIElement UIElementUnderMouseCursor { get; internal set; } + public UIElement PointerOveredElement { get; internal set; } private partial void Pick(GameTime gameTime) { @@ -37,7 +37,7 @@ private partial void Pick(GameTime gameTime) Texture renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). Viewport viewport = renderContext.ViewportState.Viewport0; - UIElement elementUnderPointer = null; + UIElement currentPointerOveredElement = null; // Prepare content required for Picking and MouseOver events PickingPrepare(); @@ -54,22 +54,22 @@ private partial void Pick(GameTime gameTime) // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) { - UIElement renderPageElementUnderPointer = null; - UpdateRenderPagePointerInput(uiDocument, viewport, ref worldViewProjection, gameTime, ref renderPageElementUnderPointer); + UIElement documentPointerOveredElement = null; + UpdateDocumentPointerInput(uiDocument, viewport, ref worldViewProjection, gameTime, ref documentPointerOveredElement); // only update result element, when this one has a value - if (renderPageElementUnderPointer != null) - elementUnderPointer = renderPageElementUnderPointer; + if (documentPointerOveredElement != null) + currentPointerOveredElement = documentPointerOveredElement; } } } PickingClear(); - UIElementUnderMouseCursor = elementUnderPointer; + PointerOveredElement = currentPointerOveredElement; } - private void UpdateRenderPagePointerInput(UIDocument uiDocument, Viewport viewport, ref Matrix worldViewProj, GameTime gameTime, ref UIElement elementUnderPointer) + private void UpdateDocumentPointerInput(UIDocument uiDocument, Viewport viewport, ref Matrix worldViewProj, GameTime gameTime, ref UIElement elementUnderPointer) { if (uiDocument.Page?.RootElement == null) return; @@ -77,8 +77,8 @@ private void UpdateRenderPagePointerInput(UIDocument uiDocument, Viewport viewpo var inverseZViewProj = worldViewProj; inverseZViewProj.Row3 = -inverseZViewProj.Row3; - elementUnderPointer = UpdateMouseOver(uiDocument, ref viewport, ref inverseZViewProj); - UpdateTouchEvents(uiDocument, ref viewport, ref inverseZViewProj, gameTime); + elementUnderPointer = UpdatePointerOver(uiDocument, ref viewport, ref inverseZViewProj); + UpdatePointerEvents(uiDocument, ref viewport, ref inverseZViewProj, gameTime); } private void PickingPrepare() @@ -136,7 +136,7 @@ private void ClearPointerEvents() /// The position of the lick on the screen in normalized (0..1, 0..1) range /// from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range /// true when the screen point of the ray would be within the bounds of the UI document; otherwise, false. - private bool TryGetRenderPageRay(Vector3 resolution, ref Viewport viewport, ref Matrix worldViewProj, Vector2 screenPosition, out Ray uiRay) + private bool TryGetDocumentRay(Vector3 resolution, ref Viewport viewport, ref Matrix worldViewProj, Vector2 screenPosition, out Ray uiRay) { uiRay = new Ray(new Vector3(float.NegativeInfinity), new Vector3(0, 1, 0)); @@ -179,7 +179,7 @@ private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix wor return uiRay; } - private void UpdateTouchEvents(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj, GameTime gameTime) + private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj, GameTime gameTime) { var rootElement = uiDocument.Page.RootElement; var intersectionPoint = Vector3.Zero; @@ -202,7 +202,7 @@ private void UpdateTouchEvents(UIDocument uiDocument, ref Viewport viewport, ref if (lastTouchPosition != currentTouchPosition) { Ray uiRay; - if (!TryGetRenderPageRay(uiDocument.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) + if (!TryGetDocumentRay(uiDocument.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) continue; currentTouchedElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); @@ -211,59 +211,54 @@ private void UpdateTouchEvents(UIDocument uiDocument, ref Viewport viewport, ref if (pointerEvent.EventType == PointerEventType.Pressed || pointerEvent.EventType == PointerEventType.Released) uiDocument.LastIntersectionPoint = intersectionPoint; - // TODO: add the pointer type to the event args? - var touchEvent = new TouchEventArgs + var uiPointerEvent = new PointerEventArgs() { - Action = TouchAction.Down, - Timestamp = time, - ScreenPosition = currentTouchPosition, - ScreenTranslation = pointerEvent.DeltaPosition, + Device = pointerEvent.Device, + PointerId = pointerEvent.PointerId, + Position = pointerEvent.Position, + DeltaPosition = pointerEvent.DeltaPosition, + DeltaTime = pointerEvent.DeltaTime, + EventType = pointerEvent.EventType, + IsDown = pointerEvent.IsDown, WorldPosition = intersectionPoint, - WorldTranslation = intersectionPoint - uiDocument.LastIntersectionPoint + WorldDeltaPosition = intersectionPoint - uiDocument.LastIntersectionPoint }; switch (pointerEvent.EventType) { case PointerEventType.Pressed: - touchEvent.Action = TouchAction.Down; - currentTouchedElement?.RaiseTouchDownEvent(touchEvent); + currentTouchedElement?.RaisePointerPressedEvent(uiPointerEvent); break; case PointerEventType.Released: - touchEvent.Action = TouchAction.Up; - // generate enter/leave events if we passed from an element to another without move events if (currentTouchedElement != lastTouchedElement) - ThrowEnterAndLeaveTouchEvents(currentTouchedElement, lastTouchedElement, touchEvent); + ThrowEnterAndLeavePointerEvents(currentTouchedElement, lastTouchedElement, uiPointerEvent); // trigger the up event - currentTouchedElement?.RaiseTouchUpEvent(touchEvent); + currentTouchedElement?.RaisePointerReleasedEvent(uiPointerEvent); break; case PointerEventType.Moved: - touchEvent.Action = TouchAction.Move; - // first notify the move event (even if the touched element changed in between it is still coherent in one of its parents) - currentTouchedElement?.RaiseTouchMoveEvent(touchEvent); + currentTouchedElement?.RaisePointerMoveEvent(uiPointerEvent); // then generate enter/leave events if we passed from an element to another if (currentTouchedElement != lastTouchedElement) - ThrowEnterAndLeaveTouchEvents(currentTouchedElement, lastTouchedElement, touchEvent); + ThrowEnterAndLeavePointerEvents(currentTouchedElement, lastTouchedElement, uiPointerEvent); break; case PointerEventType.Canceled: - touchEvent.Action = TouchAction.Move; - // generate enter/leave events if we passed from an element to another without move events if (currentTouchedElement != lastTouchedElement) - ThrowEnterAndLeaveTouchEvents(currentTouchedElement, lastTouchedElement, touchEvent); + ThrowEnterAndLeavePointerEvents(currentTouchedElement, lastTouchedElement, uiPointerEvent); // then raise leave event to all the hierarchy of the previously selected element. var element = currentTouchedElement; while (element != null) { - if (element.IsTouched) - element.RaiseTouchLeaveEvent(touchEvent); + if (element.IsPointerDown) + element.RaisePointerLeaveEvent(uiPointerEvent); element = element.VisualParent; } break; @@ -277,7 +272,7 @@ private void UpdateTouchEvents(UIDocument uiDocument, ref Viewport viewport, ref } } - private UIElement UpdateMouseOver(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj) + private UIElement UpdatePointerOver(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj) { if (input == null || !input.HasMouse) return null; @@ -287,55 +282,54 @@ private UIElement UpdateMouseOver(UIDocument uiDocument, ref Viewport viewport, var rootElement = uiDocument.Page.RootElement; var lastPointerOverElement = uiDocument.LastPointerOverElement; - UIElement mouseOverElement = lastPointerOverElement; + UIElement pointerOveredElement = lastPointerOverElement; // determine currently overred element. if (mousePosition != uiDocument.LastMousePosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) { Ray uiRay; - if (TryGetRenderPageRay(uiDocument.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) - mouseOverElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); + if (TryGetDocumentRay(uiDocument.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) + pointerOveredElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); else - mouseOverElement = null; + pointerOveredElement = null; } - // find the common parent between current and last overred elements - var commonElement = FindCommonParent(mouseOverElement, lastPointerOverElement); - - // disable mouse over state to previously overred hierarchy + // Find the common parent between current and last overed elements. + var commonElement = FindCommonParent(pointerOveredElement, lastPointerOverElement); + + // Disable mouse over state to previously overed hierarchy. var parent = lastPointerOverElement; while (parent != commonElement && parent != null) { parent.RequiresMouseOverUpdate = false; - parent.MouseOverState = MouseOverState.MouseOverNone; + parent.PointerOverState = PointerOverState.None; parent = parent.VisualParent; } - - // enable mouse over state to currently overred hierarchy - if (mouseOverElement != null) + // Enable pointer over state to currently overed hierarchy. + if (pointerOveredElement != null) { - // the element itself - mouseOverElement.MouseOverState = MouseOverState.MouseOverElement; + // The element itself. + pointerOveredElement.PointerOverState = PointerOverState.Self; - // its hierarchy - parent = mouseOverElement.VisualParent; + // Its hierarchy. + parent = pointerOveredElement.VisualParent; while (parent != null) { if (parent.IsHierarchyEnabled) - parent.MouseOverState = MouseOverState.MouseOverChild; + parent.PointerOverState = PointerOverState.Child; parent = parent.VisualParent; } } // update cached values - uiDocument.LastPointerOverElement = mouseOverElement; + uiDocument.LastPointerOverElement = pointerOveredElement; uiDocument.LastMousePosition = mousePosition; - return mouseOverElement; + return pointerOveredElement; } private UIElement FindCommonParent(UIElement element1, UIElement element2) @@ -357,7 +351,7 @@ private UIElement FindCommonParent(UIElement element1, UIElement element2) return commonElement; } - private void ThrowEnterAndLeaveTouchEvents(UIElement currentElement, UIElement previousElement, TouchEventArgs touchEvent) + private void ThrowEnterAndLeavePointerEvents(UIElement currentElement, UIElement previousElement, PointerEventArgs pointerArgs) { var commonElement = FindCommonParent(currentElement, previousElement); @@ -365,10 +359,10 @@ private void ThrowEnterAndLeaveTouchEvents(UIElement currentElement, UIElement p var previousElementParent = previousElement; while (previousElementParent != commonElement && previousElementParent != null) { - if (previousElementParent.IsHierarchyEnabled && previousElementParent.IsTouched) + if (previousElementParent.IsHierarchyEnabled && previousElementParent.IsPointerDown) { - touchEvent.Handled = false; // reset 'handled' because it corresponds to another event - previousElementParent.RaiseTouchLeaveEvent(touchEvent); + pointerArgs.Handled = false; // reset 'handled' because it corresponds to another event + previousElementParent.RaisePointerLeaveEvent(pointerArgs); } previousElementParent = previousElementParent.VisualParent; } @@ -377,10 +371,10 @@ private void ThrowEnterAndLeaveTouchEvents(UIElement currentElement, UIElement p var newElementParent = currentElement; while (newElementParent != commonElement && newElementParent != null) { - if (newElementParent.IsHierarchyEnabled && !newElementParent.IsTouched) + if (newElementParent.IsHierarchyEnabled && !newElementParent.IsPointerDown) { - touchEvent.Handled = false; // reset 'handled' because it corresponds to another event - newElementParent.RaiseTouchEnterEvent(touchEvent); + pointerArgs.Handled = false; // reset 'handled' because it corresponds to another event + newElementParent.RaisePointerEnterEvent(pointerArgs); } newElementParent = newElementParent.VisualParent; } From caabcb9dc8eef767f368c138cae288fef7f185f8 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Sun, 29 Sep 2024 19:46:49 -0700 Subject: [PATCH 13/20] Fixed UI editor selection and adorners not working --- sources/engine/Stride.UI/UISystem.Picking.cs | 51 +++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.Picking.cs index bdb0d427b7..1bbf527096 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.Picking.cs @@ -6,10 +6,12 @@ using System.Linq; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; +using Stride.Engine; using Stride.Games; using Stride.Graphics; using Stride.Input; using Stride.Rendering; +using Stride.Rendering.Compositing; using Stride.Rendering.UI; namespace Stride.UI @@ -41,26 +43,17 @@ private partial void Pick(GameTime gameTime) // Prepare content required for Picking and MouseOver events PickingPrepare(); - - foreach (var cameraSlot in sceneSystem.GraphicsCompositor.Cameras) + + // In the editor the Cameras in GraphicsCompositor are not set, so this is required for selection to work in the UI editor. + if (sceneSystem.GraphicsCompositor.Game is SceneExternalCameraRenderer externalCameraRenderer) + { + ProcessUIDocuments(externalCameraRenderer.ExternalCamera, ref viewport, renderTarget, gameTime, ref currentPointerOveredElement); + } + else { - foreach (var uiDocument in documents) + foreach (var cameraSlot in sceneSystem.GraphicsCompositor.Cameras) { - if (!uiDocument.Enabled) - continue; - - Matrix worldViewProjection = uiDocument.GetWorldViewProjection(cameraSlot.Camera, renderTarget); - - // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) - using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) - { - UIElement documentPointerOveredElement = null; - UpdateDocumentPointerInput(uiDocument, viewport, ref worldViewProjection, gameTime, ref documentPointerOveredElement); - - // only update result element, when this one has a value - if (documentPointerOveredElement != null) - currentPointerOveredElement = documentPointerOveredElement; - } + ProcessUIDocuments(cameraSlot.Camera, ref viewport, renderTarget, gameTime, ref currentPointerOveredElement); } } @@ -69,6 +62,28 @@ private partial void Pick(GameTime gameTime) PointerOveredElement = currentPointerOveredElement; } + private void ProcessUIDocuments(CameraComponent camera, ref Viewport viewport, Texture renderTarget, GameTime gameTime, ref UIElement currentPointerOveredElement) + { + foreach (var uiDocument in documents) + { + if (!uiDocument.Enabled) + continue; + + Matrix worldViewProjection = uiDocument.GetWorldViewProjection(camera, renderTarget); + + // Check if the current UI component is being picked based on the current ViewParameters (used to draw this element) + using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) + { + UIElement documentPointerOveredElement = null; + UpdateDocumentPointerInput(uiDocument, viewport, ref worldViewProjection, gameTime, ref documentPointerOveredElement); + + // only update result element, when this one has a value + if (documentPointerOveredElement != null) + currentPointerOveredElement = documentPointerOveredElement; + } + } + } + private void UpdateDocumentPointerInput(UIDocument uiDocument, Viewport viewport, ref Matrix worldViewProj, GameTime gameTime, ref UIElement elementUnderPointer) { if (uiDocument.Page?.RootElement == null) From 7fabf47148aeb4dd9ef6768489f012e869dad018 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Wed, 2 Oct 2024 20:01:58 -0700 Subject: [PATCH 14/20] Cleanup and commenting of UI system refactor --- sources/engine/Stride.UI/PointerEventArgs.cs | 3 + ...em.Picking.cs => UISystem.PointerInput.cs} | 76 +++++++++---------- sources/engine/Stride.UI/UISystem.cs | 4 +- 3 files changed, 39 insertions(+), 44 deletions(-) rename sources/engine/Stride.UI/{UISystem.Picking.cs => UISystem.PointerInput.cs} (91%) diff --git a/sources/engine/Stride.UI/PointerEventArgs.cs b/sources/engine/Stride.UI/PointerEventArgs.cs index e131c93b0d..49cdbb0007 100644 --- a/sources/engine/Stride.UI/PointerEventArgs.cs +++ b/sources/engine/Stride.UI/PointerEventArgs.cs @@ -8,6 +8,9 @@ namespace Stride.UI; +/// +/// Routed event arguments associated with a pointer event. +/// public class PointerEventArgs : RoutedEventArgs { // Pointer properties... diff --git a/sources/engine/Stride.UI/UISystem.Picking.cs b/sources/engine/Stride.UI/UISystem.PointerInput.cs similarity index 91% rename from sources/engine/Stride.UI/UISystem.Picking.cs rename to sources/engine/Stride.UI/UISystem.PointerInput.cs index 1bbf527096..af828b384b 100644 --- a/sources/engine/Stride.UI/UISystem.Picking.cs +++ b/sources/engine/Stride.UI/UISystem.PointerInput.cs @@ -7,7 +7,6 @@ using Stride.Core.Diagnostics; using Stride.Core.Mathematics; using Stride.Engine; -using Stride.Games; using Stride.Graphics; using Stride.Input; using Stride.Rendering; @@ -31,38 +30,42 @@ public partial class UISystem /// public UIElement PointerOveredElement { get; internal set; } - private partial void Pick(GameTime gameTime) + private partial void UpdatePointerInput() { if (renderContext == null || sceneSystem == null || sceneSystem.GraphicsCompositor == null) return; Texture renderTarget = renderContext.GraphicsDevice.Presenter.BackBuffer; - // TODO: Not sure if this would support VR. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). + + // TODO: Not sure if this supports VR. Update comment after testing. If it doesn't, look at ForwardRenderer.DrawCore for how to (potentially). Viewport viewport = renderContext.ViewportState.Viewport0; + UIElement currentPointerOveredElement = null; - // Prepare content required for Picking and MouseOver events - PickingPrepare(); + // Compact all the pointer events that happened since last frame to avoid performing useless hit tests. + CompactPointerEvents(); // In the editor the Cameras in GraphicsCompositor are not set, so this is required for selection to work in the UI editor. if (sceneSystem.GraphicsCompositor.Game is SceneExternalCameraRenderer externalCameraRenderer) { - ProcessUIDocuments(externalCameraRenderer.ExternalCamera, ref viewport, renderTarget, gameTime, ref currentPointerOveredElement); + ProcessUIDocuments(externalCameraRenderer.ExternalCamera, ref viewport, renderTarget, ref currentPointerOveredElement); } else { + // Handle input on the UI for each camera used in the scene (doing hit tests from the perspective of each camera). foreach (var cameraSlot in sceneSystem.GraphicsCompositor.Cameras) { - ProcessUIDocuments(cameraSlot.Camera, ref viewport, renderTarget, gameTime, ref currentPointerOveredElement); + ProcessUIDocuments(cameraSlot.Camera, ref viewport, renderTarget, ref currentPointerOveredElement); } } - PickingClear(); + // clear the list of compacted pointer events of time frame + ClearPointerEvents(); PointerOveredElement = currentPointerOveredElement; } - private void ProcessUIDocuments(CameraComponent camera, ref Viewport viewport, Texture renderTarget, GameTime gameTime, ref UIElement currentPointerOveredElement) + private void ProcessUIDocuments(CameraComponent camera, ref Viewport viewport, Texture renderTarget, ref UIElement currentPointerOveredElement) { foreach (var uiDocument in documents) { @@ -75,7 +78,7 @@ private void ProcessUIDocuments(CameraComponent camera, ref Viewport viewport, T using (Profiler.Begin(UIProfilerKeys.TouchEventsUpdate)) { UIElement documentPointerOveredElement = null; - UpdateDocumentPointerInput(uiDocument, viewport, ref worldViewProjection, gameTime, ref documentPointerOveredElement); + UpdateDocumentPointerInput(uiDocument, viewport, ref worldViewProjection, ref documentPointerOveredElement); // only update result element, when this one has a value if (documentPointerOveredElement != null) @@ -84,7 +87,7 @@ private void ProcessUIDocuments(CameraComponent camera, ref Viewport viewport, T } } - private void UpdateDocumentPointerInput(UIDocument uiDocument, Viewport viewport, ref Matrix worldViewProj, GameTime gameTime, ref UIElement elementUnderPointer) + private void UpdateDocumentPointerInput(UIDocument uiDocument, Viewport viewport, ref Matrix worldViewProj, ref UIElement elementUnderPointer) { if (uiDocument.Page?.RootElement == null) return; @@ -93,19 +96,7 @@ private void UpdateDocumentPointerInput(UIDocument uiDocument, Viewport viewport inverseZViewProj.Row3 = -inverseZViewProj.Row3; elementUnderPointer = UpdatePointerOver(uiDocument, ref viewport, ref inverseZViewProj); - UpdatePointerEvents(uiDocument, ref viewport, ref inverseZViewProj, gameTime); - } - - private void PickingPrepare() - { - // compact all the pointer events that happened since last frame to avoid performing useless hit tests. - CompactPointerEvents(); - } - - private void PickingClear() - { - // clear the list of compacted pointer events of time frame - ClearPointerEvents(); + UpdatePointerEvents(uiDocument, ref viewport, ref inverseZViewProj); } private void CompactPointerEvents() @@ -148,7 +139,7 @@ private void ClearPointerEvents() /// The bounds to test within /// The in which the component is being rendered /// - /// The position of the lick on the screen in normalized (0..1, 0..1) range + /// The position on the screen in normalized (0..1, 0..1) range /// from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range /// true when the screen point of the ray would be within the bounds of the UI document; otherwise, false. private bool TryGetDocumentRay(Vector3 resolution, ref Viewport viewport, ref Matrix worldViewProj, Vector2 screenPosition, out Ray uiRay) @@ -157,10 +148,10 @@ private bool TryGetDocumentRay(Vector3 resolution, ref Viewport viewport, ref Ma // TODO XK-3367 This only works for a single view - // Get a ray in object (RenderUIElement) space + // Get a ray in object (UIDocument) space var ray = GetWorldRay(ref viewport, screenPosition, ref worldViewProj); - // If the screen point is outside the canvas ignore any further testing + // If the screen point is outside the document ignore any further testing. var dist = -ray.Position.Z / ray.Direction.Z; if (Math.Abs(ray.Position.X + ray.Direction.X * dist) > resolution.X * 0.5f || Math.Abs(ray.Position.Y + ray.Direction.Y * dist) > resolution.Y * 0.5f) @@ -171,11 +162,11 @@ private bool TryGetDocumentRay(Vector3 resolution, ref Viewport viewport, ref Ma } /// - /// Creates a ray in object space based on a screen position and a previously rendered object's WorldViewProjection matrix + /// Creates a ray in object space based on a screen position and a WorldViewProjection matrix /// - /// The viewport in which the object was rendered - /// The click position on screen in normalized (0..1, 0..1) range - /// The WorldViewProjection matrix with which the object was last rendered in the view + /// The viewport used to transform the screen position to a ray. + /// The position on screen in normalized (0..1, 0..1) range + /// The WorldViewProjection matrix of the UIDocument for the current camera. /// private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix worldViewProj) { @@ -194,7 +185,7 @@ private Ray GetWorldRay(ref Viewport viewport, Vector2 screenPos, ref Matrix wor return uiRay; } - private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj, GameTime gameTime) + private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj) { var rootElement = uiDocument.Page.RootElement; var intersectionPoint = Vector3.Zero; @@ -208,8 +199,6 @@ private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, r if (lastTouchedElement == null && pointerEvent.EventType != PointerEventType.Pressed) continue; - var time = gameTime.Total; - var currentTouchPosition = pointerEvent.Position; var currentTouchedElement = lastTouchedElement; @@ -225,7 +214,8 @@ private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, r if (pointerEvent.EventType == PointerEventType.Pressed || pointerEvent.EventType == PointerEventType.Released) uiDocument.LastIntersectionPoint = intersectionPoint; - + + // TODO: Add RoutedEventArgs pooling to avoid allocation every event (like every mouse move). var uiPointerEvent = new PointerEventArgs() { Device = pointerEvent.Device, @@ -289,7 +279,7 @@ private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, r private UIElement UpdatePointerOver(UIDocument uiDocument, ref Viewport viewport, ref Matrix worldViewProj) { - if (input == null || !input.HasMouse) + if (input == null || !input.HasMouse) // no input for thumbnails return null; var intersectionPoint = Vector3.Zero; @@ -299,11 +289,11 @@ private UIElement UpdatePointerOver(UIDocument uiDocument, ref Viewport viewport UIElement pointerOveredElement = lastPointerOverElement; - // determine currently overred element. + // Determine currently overed element. if (mousePosition != uiDocument.LastMousePosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) { Ray uiRay; - + if (TryGetDocumentRay(uiDocument.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) pointerOveredElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); else @@ -394,21 +384,23 @@ private void ThrowEnterAndLeavePointerEvents(UIElement currentElement, UIElement newElementParent = newElementParent.VisualParent; } } + + // TODO Refactor static utility methods to be more user friendly. /// - /// Gets the element with which the clickRay intersects, or null if none is found + /// Gets the element with which the ray intersects, or null if none is found /// /// The root from which it should test - /// from the click in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range + /// from the point in object space of the ui component in (-Resolution.X/2 .. Resolution.X/2, -Resolution.Y/2 .. Resolution.Y/2) range /// /// Intersection point between the ray and the element /// The with which the ray intersects - public static UIElement GetElementAtScreenPosition(UIElement rootElement, ref Ray clickRay, ref Matrix worldViewProj, ref Vector3 intersectionPoint) + public static UIElement GetElementAtScreenPosition(UIElement rootElement, ref Ray uiRay, 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); + PerformRecursiveHitTest(rootElement, ref uiRay, ref worldViewProj, ref clickedElement, ref intersectionPoint, ref smallestDepth, ref highestDepthBias); return clickedElement; } diff --git a/sources/engine/Stride.UI/UISystem.cs b/sources/engine/Stride.UI/UISystem.cs index c7bce464f9..c97dfe1788 100644 --- a/sources/engine/Stride.UI/UISystem.cs +++ b/sources/engine/Stride.UI/UISystem.cs @@ -130,12 +130,12 @@ public override void Update(GameTime gameTime) if (renderContext == null) renderContext = RenderContext.GetShared(Services); - Pick(gameTime); + UpdatePointerInput(); UpdateKeyEvents(); } - private partial void Pick(GameTime gameTime); + private partial void UpdatePointerInput(); private void UpdateKeyEvents() { From 955ebee607334069be8c1d8c022a889ad0c47f01 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Thu, 3 Oct 2024 09:41:59 -0700 Subject: [PATCH 15/20] Removed accidental extra space added in UIRenderFeature --- sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs index c8bf2f58c0..48e93e4e9e 100644 --- a/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs +++ b/sources/engine/Stride.UI/Rendering/UI/UIRenderFeature.cs @@ -61,7 +61,7 @@ protected override void InitializeCore() rendererManager = new RendererManager(new DefaultRenderersFactory(RenderSystem.Services)); - batch = uiSystem.Batch; + batch = uiSystem.Batch; } public override void Draw(RenderDrawContext context, RenderView renderView, RenderViewStage renderViewStage, int startIndex, int endIndex) From 988ef2f604f1abb2edfb28ed210d501777cd1f22 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Tue, 22 Oct 2024 14:01:14 -0700 Subject: [PATCH 16/20] Added missing UI comments and renamed Touch to Pointer where missed --- .../UIEditor/Adorners/SizingAdorner.cs | 4 +- sources/engine/Stride.Input/PointerEvent.cs | 8 ++- sources/engine/Stride.UI/Controls/Slider.cs | 14 ++--- sources/engine/Stride.UI/PointerEventArgs.cs | 14 ++++- .../engine/Stride.UI/UIComponentProcessor.cs | 29 +++++----- sources/engine/Stride.UI/UIDocument.cs | 41 ++++++++++++- sources/engine/Stride.UI/UIElement.Events.cs | 57 ++++++++++--------- .../engine/Stride.UI/UISystem.PointerInput.cs | 52 ++++++++--------- 8 files changed, 137 insertions(+), 82 deletions(-) diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs index 9c77f0ae17..0097313ee5 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/UIEditor/Adorners/SizingAdorner.cs @@ -27,7 +27,7 @@ public SizingAdorner(UIEditorGameAdornerService service, UIElement gameSideEleme ResizingDirection = resizingDirection; // support for mouse over cursor - Visual.MouseOverStateChanged += MouseOverStateChanged; + Visual.MouseOverStateChanged += PointerOverStateChanged; } public ResizingDirection ResizingDirection @@ -147,7 +147,7 @@ void IResizingAdorner.OnResizingCompleted() isDragging = false; } - private void MouseOverStateChanged(object sender, PropertyChangedArgs e) + private void PointerOverStateChanged(object sender, PropertyChangedArgs e) { if (isDragging) return; diff --git a/sources/engine/Stride.Input/PointerEvent.cs b/sources/engine/Stride.Input/PointerEvent.cs index fe31b84a15..76e9d46f73 100644 --- a/sources/engine/Stride.Input/PointerEvent.cs +++ b/sources/engine/Stride.Input/PointerEvent.cs @@ -8,7 +8,7 @@ namespace Stride.Input { /// - /// A pointer event. + /// A pointer event. A pointer is a mouse, pen, finger, or other touch. /// public class PointerEvent : InputEvent { @@ -23,24 +23,28 @@ public class PointerEvent : InputEvent /// Gets the absolute screen position of the pointer. /// /// The absolute delta position. + /// AbsolutePosition is between [0, SurfaceSize]. (0, 0) is the left top corner, (SurfaceSize.X, SurfaceSize.Y) is the right bottom corner. public Vector2 AbsolutePosition => Position * Pointer.SurfaceSize; /// - /// Gets the normalized screen position of the pointer. Position is normalized between [0,1]. (0,0) is the left top corner, (1,1) is the right bottom corner. + /// Gets the normalized screen position of the pointer. /// /// The position. + /// Position is normalized between [0,1]. (0,0) is the left top corner, (1,1) is the right bottom corner. public Vector2 Position { get; internal set; } /// /// Gets the absolute delta position of the pointer since the previous frame. /// /// The absolute delta position. + /// AbsoluteDeltaPosition position space is between [0, SurfaceSize]. (0, 0) is the left top corner, (SurfaceSize.X, SurfaceSize.Y) is the right bottom corner. public Vector2 AbsoluteDeltaPosition => DeltaPosition * Pointer.SurfaceSize; /// /// Gets the delta position of the pointer since the previous frame. /// /// The delta position. + /// DeltaPosition position space is normalized between [0,1]. (0,0) is the left top corner, (1,1) is the right bottom corner. public Vector2 DeltaPosition { get; internal set; } /// diff --git a/sources/engine/Stride.UI/Controls/Slider.cs b/sources/engine/Stride.UI/Controls/Slider.cs index 7f92dd120b..112c57de60 100644 --- a/sources/engine/Stride.UI/Controls/Slider.cs +++ b/sources/engine/Stride.UI/Controls/Slider.cs @@ -392,7 +392,7 @@ protected override void OnPointerPressed(PointerEventArgs args) { base.OnPointerPressed(args); - SetValueFromTouchPosition(args.WorldPosition); + SetValueFromPosition(args.WorldPosition); IsTouchedDown = true; } @@ -416,7 +416,7 @@ protected override void OnPointerMove(PointerEventArgs args) if (IsTouchedDown) { - SetValueFromTouchPosition(args.WorldPosition); + SetValueFromPosition(args.WorldPosition); } } @@ -431,16 +431,16 @@ internal override void OnKeyDown(KeyEventArgs args) } /// - /// Set from the world position of a touch event. + /// Set from a world position. /// - /// The world position of the touch - protected void SetValueFromTouchPosition(Vector3 touchPostionWorld) + /// The world position. + protected void SetValueFromPosition(Vector3 worldPosition) { var axis = (int)Orientation; var offsets = TrackStartingOffsets; var elementSize = RenderSize[axis]; - var touchPosition = touchPostionWorld[axis] - WorldMatrixInternal[12 + axis] + elementSize / 2; - var ratio = (touchPosition - offsets.X) / (elementSize - offsets.X - offsets.Y); + var position = worldPosition[axis] - WorldMatrixInternal[12 + axis] + elementSize / 2; + var ratio = (position - offsets.X) / (elementSize - offsets.X - offsets.Y); Value = MathUtil.Lerp(Minimum, Maximum, Orientation == Orientation.Vertical ^ IsDirectionReversed ? 1 - ratio : ratio); } diff --git a/sources/engine/Stride.UI/PointerEventArgs.cs b/sources/engine/Stride.UI/PointerEventArgs.cs index 49cdbb0007..011175e2c5 100644 --- a/sources/engine/Stride.UI/PointerEventArgs.cs +++ b/sources/engine/Stride.UI/PointerEventArgs.cs @@ -48,13 +48,23 @@ public class PointerEventArgs : RoutedEventArgs // UI specific properties... /// - /// Gets the position of the touch in the UI virtual world space. + /// Gets the position of the pointer in the UI virtual world space (in virtual pixels). /// + /// + /// WorldPosition is between [-resolution/2, +resolution/2]. (-resolution.X/2, -resolution.Y/2) is the top left corner, + /// (+resolution.X/2, +resolution.Y/2) is the bottom right corner, with (0, 0) being the center. + /// resolution is the resolution of the page. The Z axis is un-used. + /// public Vector3 WorldPosition { get; internal set; } /// - /// Gets the translation of the touch in the UI virtual world space. + /// Gets the translation of the pointer in the UI virtual world space (in virtual pixels). /// + /// + /// World space is between [-resolution/2, +resolution/2]. (-resolution.X/2, -resolution.Y/2) is the top left corner, + /// (+resolution.X/2, +resolution.Y/2) is the bottom right corner, with (0, 0) being the center. + /// resolution is the resolution of the page. The Z axis is un-used. + /// public Vector3 WorldDeltaPosition { get; internal set; } public PointerEventArgs Clone() diff --git a/sources/engine/Stride.UI/UIComponentProcessor.cs b/sources/engine/Stride.UI/UIComponentProcessor.cs index 08b690322d..aab425063d 100644 --- a/sources/engine/Stride.UI/UIComponentProcessor.cs +++ b/sources/engine/Stride.UI/UIComponentProcessor.cs @@ -7,7 +7,7 @@ namespace Stride.UI; /// -/// The processor in charge of updating entities that have s. +/// The processor in charge of updating s in the scene. /// public class UIComponentProcessor : EntityProcessor { @@ -39,18 +39,21 @@ public override void Update(GameTime time) var uiDocument = componentDocumentKeyPair.Value; uiDocument.Enabled = uiComponent.Enabled; - - uiDocument.WorldMatrix = uiComponent.Entity.Transform.WorldMatrix; - uiDocument.RenderGroup = uiComponent.RenderGroup; - uiDocument.Page = uiComponent.Page; - uiDocument.Sampler = uiComponent.Sampler; - uiDocument.IsFullScreen = uiComponent.IsFullScreen; - uiDocument.Resolution = uiComponent.Resolution; - uiDocument.Size = uiComponent.Size; - uiDocument.ResolutionStretch = uiComponent.ResolutionStretch; - uiDocument.IsBillboard = uiComponent.IsBillboard; - uiDocument.SnapText = uiComponent.SnapText; - uiDocument.IsFixedSize = uiComponent.IsFixedSize; + + if (uiDocument.Enabled) + { + uiDocument.WorldMatrix = uiComponent.Entity.Transform.WorldMatrix; + uiDocument.RenderGroup = uiComponent.RenderGroup; + uiDocument.Page = uiComponent.Page; + uiDocument.Sampler = uiComponent.Sampler; + uiDocument.IsFullScreen = uiComponent.IsFullScreen; + uiDocument.Resolution = uiComponent.Resolution; + uiDocument.Size = uiComponent.Size; + uiDocument.ResolutionStretch = uiComponent.ResolutionStretch; + uiDocument.IsBillboard = uiComponent.IsBillboard; + uiDocument.SnapText = uiComponent.SnapText; + uiDocument.IsFixedSize = uiComponent.IsFixedSize; + } } } diff --git a/sources/engine/Stride.UI/UIDocument.cs b/sources/engine/Stride.UI/UIDocument.cs index d5ac253026..c7063f3710 100644 --- a/sources/engine/Stride.UI/UIDocument.cs +++ b/sources/engine/Stride.UI/UIDocument.cs @@ -33,13 +33,47 @@ public class UIDocument /// public UIPage Page { get; set; } + /// + /// Specifies the sampling method to be used for this UI. + /// public UIElementSampler Sampler { get; set; } + + /// + /// Determines whether the UI should be full screen. + /// public bool IsFullScreen { get; set; } + + /// + /// The virtual resolution of the UI in virtual pixels. + /// public Vector3 Resolution { get; set; } + + /// + /// The actual size of the UI in world units. This value is ignored in fullscreen mode. + /// public Vector3 Size { get; set; } + + /// + /// specifies how the virtual resolution value should be interpreted. + /// public ResolutionStretch ResolutionStretch { get; set; } + + /// + /// Determines whether the UI should be displayed as billboard. + /// + /// A billboard is rendered in world space and automatically rotated parallel to the screen. public bool IsBillboard { get; set; } + + /// + /// Determines whether the UI texts should be snapped to closest pixel. + /// + /// If true, all the text of the UI is snapped to the closest pixel (pixel perfect). This is only effective if IsFullScreen or IsFixedSize is set, as well as IsBillboard. public bool SnapText { get; set; } + + /// + /// Determines whether the UI should be always a fixed size on the screen. + /// + /// A fixed size component with a height of 1 unit will be 0.1 of the screen size. public bool IsFixedSize { get; set; } /// @@ -53,9 +87,12 @@ public class UIDocument public UIElement LastPointerOverElement { get; set; } /// - /// Last element which received a touch/click event + /// Last element which received a pointer event /// - public UIElement LastTouchedElement { get; set; } + public UIElement LastInteractedElement { get; set; } + /// + /// The last point in UI virtual world space that the pointer was over an element. + /// public Vector3 LastIntersectionPoint { get; set; } } diff --git a/sources/engine/Stride.UI/UIElement.Events.cs b/sources/engine/Stride.UI/UIElement.Events.cs index 7a5a2e8f11..9a2ecd8e73 100644 --- a/sources/engine/Stride.UI/UIElement.Events.cs +++ b/sources/engine/Stride.UI/UIElement.Events.cs @@ -72,16 +72,18 @@ static UIElement() } /// - /// Gets a value indicating whether the is currently touched by the user. + /// Gets a value indicating whether a pointer is pressed down on the . /// [DataMemberIgnore] public bool IsPointerDown { get; internal set; } /// - /// Gets the current state of the mouse over the UI element. + /// Gets the current state of the pointer over the UI element. /// - /// Only elements that can be clicked by user can have the PointerOverState.Self value. - /// That is element that have set to true + /// + /// Only elements that can be clicked by user can have the PointerOverState.Self value. + /// That is element that have set to true + /// [DataMemberIgnore] public PointerOverState PointerOverState { @@ -233,9 +235,9 @@ public void RemoveHandler(RoutedEvent routedEvent, EventHandler handler public event PropertyChangedHandler MouseOverStateChanged; /// - /// Occurs when the user starts touching the . That is when he moves its finger down from the element. + /// Occurs when the user starts pressing the with a pointer. /// - /// A click event is tunneling + /// A press event is tunneling public event EventHandler PreviewPointerPressed { add { AddHandler(PreviewPointerPressedEvent, value); } @@ -243,10 +245,9 @@ public event EventHandler PreviewPointerPressed } /// - /// Occurs when the user moves its finger on the . - /// That is when his finger was already on the element and moved from its previous position. + /// Occurs when the user moves a pointer on the . /// - /// A click event is tunneling + /// A move event is tunneling public event EventHandler PreviewPointerMove { add { AddHandler(PreviewPointerMoveEvent, value); } @@ -254,30 +255,30 @@ public event EventHandler PreviewPointerMove } /// - /// Occurs when the user stops touching the . That is when he moves its finger up from the element. + /// Occurs when the user stops pressing the . /// - /// A click event is tunneling - public event EventHandler PreviewPointerUp + /// A release event is tunneling + public event EventHandler PreviewPointerReleased { add { AddHandler(PreviewPointerReleasedEvent, value); } remove { RemoveHandler(PreviewPointerReleasedEvent, value); } } /// - /// Occurs when the user starts touching the . That is when he moves its finger down from the element. + /// Occurs when the user starts pressing the . /// - /// A click event is bubbling - public event EventHandler PointerDown + /// A press event is bubbling. + public event EventHandler PointerPressed { add { AddHandler(PointerPressedEvent, value); } remove { RemoveHandler(PointerPressedEvent, value); } } /// - /// Occurs when the user enters its finger into . - /// That is when his finger was on the screen outside of the element and moved inside the element. + /// Occurs when the user moves a pointer into . + /// That is when a pointer was on the screen outside of the element and moved inside the element. /// - /// A click event is bubbling + /// A enter event is bubbling public event EventHandler PointerEnter { add { AddHandler(PointerEnterEvent, value); } @@ -285,10 +286,10 @@ public event EventHandler PointerEnter } /// - /// Occurs when the user leaves its finger from the . - /// That is when his finger was inside of the element and moved on the screen outside of the element. + /// Occurs when the user moves a pointer from the . + /// That is when a pointer was inside of the element and moved on the screen outside of the element. /// - /// A click event is bubbling + /// A leave event is bubbling public event EventHandler PointerLeave { add { AddHandler(PointerLeaveEvent, value); } @@ -296,10 +297,10 @@ public event EventHandler PointerLeave } /// - /// Occurs when the user move its finger inside the . - /// That is when his finger was already on the element and moved from its previous position. + /// Occurs when the user move a pointer inside the . + /// That is when a pointer was already on the element and moved from its previous position. /// - /// A click event is bubbling + /// A move event is bubbling public event EventHandler PointerMove { add { AddHandler(PointerMoveEvent, value); } @@ -307,9 +308,9 @@ public event EventHandler PointerMove } /// - /// Occurs when the user stops touching the . That is when he moves its finger up from the element. + /// Occurs when the user stops pressing the . /// - /// A click event is bubbling + /// A release event is bubbling public event EventHandler PointerReleased { add { AddHandler(PointerReleaseEvent, value); } @@ -467,7 +468,7 @@ private static void PreviewPointerReleasedClassHandler(object sender, PointerEve } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event @@ -484,7 +485,7 @@ private static void PointerPressedClassHandler(object sender, PointerEventArgs a } /// - /// The class handler of the event . + /// The class handler of the event . /// This method can be overridden in inherited classes to perform actions common to all instances of a class. /// /// The arguments of the event diff --git a/sources/engine/Stride.UI/UISystem.PointerInput.cs b/sources/engine/Stride.UI/UISystem.PointerInput.cs index af828b384b..708384b0a4 100644 --- a/sources/engine/Stride.UI/UISystem.PointerInput.cs +++ b/sources/engine/Stride.UI/UISystem.PointerInput.cs @@ -24,9 +24,9 @@ public partial class UISystem private readonly HashSet documents = new HashSet(); /// - /// Represents the UI-element that's currently under the mouse cursor. + /// Represents the UI-element that's currently under the pointer. /// Only elements with CanBeHitByUser == true are taken into account. - /// Last processed element_state / ?UIComponent? with a valid element will be used. + /// Last processed UI Page with a valid element will be used. /// public UIElement PointerOveredElement { get; internal set; } @@ -189,27 +189,27 @@ private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, r { var rootElement = uiDocument.Page.RootElement; var intersectionPoint = Vector3.Zero; - var lastTouchPosition = new Vector2(float.NegativeInfinity); + var lastPointerPosition = new Vector2(float.NegativeInfinity); - // analyze pointer event input and trigger UI touch events depending on hit Tests + // analyze pointer event input and trigger UI pointer events depending on hit Tests foreach (var pointerEvent in compactedPointerEvents) { // performance optimization: skip all the events that started outside of the UI - var lastTouchedElement = uiDocument.LastTouchedElement; - if (lastTouchedElement == null && pointerEvent.EventType != PointerEventType.Pressed) + var lastInteractedElement = uiDocument.LastInteractedElement; + if (lastInteractedElement == null && pointerEvent.EventType != PointerEventType.Pressed) continue; - var currentTouchPosition = pointerEvent.Position; - var currentTouchedElement = lastTouchedElement; + var currentPointerPosition = pointerEvent.Position; + var currentInteractedElement = lastInteractedElement; - // re-calculate the element under cursor if click position changed. - if (lastTouchPosition != currentTouchPosition) + // re-calculate the element under pointer if position changed. + if (lastPointerPosition != currentPointerPosition) { Ray uiRay; - if (!TryGetDocumentRay(uiDocument.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) + if (!TryGetDocumentRay(uiDocument.Resolution, ref viewport, ref worldViewProj, currentPointerPosition, out uiRay)) continue; - currentTouchedElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); + currentInteractedElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); } if (pointerEvent.EventType == PointerEventType.Pressed || pointerEvent.EventType == PointerEventType.Released) @@ -232,34 +232,34 @@ private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, r switch (pointerEvent.EventType) { case PointerEventType.Pressed: - currentTouchedElement?.RaisePointerPressedEvent(uiPointerEvent); + currentInteractedElement?.RaisePointerPressedEvent(uiPointerEvent); break; case PointerEventType.Released: // generate enter/leave events if we passed from an element to another without move events - if (currentTouchedElement != lastTouchedElement) - ThrowEnterAndLeavePointerEvents(currentTouchedElement, lastTouchedElement, uiPointerEvent); + if (currentInteractedElement != lastInteractedElement) + ThrowEnterAndPointerLeaveEvents(currentInteractedElement, lastInteractedElement, uiPointerEvent); // trigger the up event - currentTouchedElement?.RaisePointerReleasedEvent(uiPointerEvent); + currentInteractedElement?.RaisePointerReleasedEvent(uiPointerEvent); break; case PointerEventType.Moved: - // first notify the move event (even if the touched element changed in between it is still coherent in one of its parents) - currentTouchedElement?.RaisePointerMoveEvent(uiPointerEvent); + // first notify the move event (even if the interacted element changed in between it is still coherent in one of its parents) + currentInteractedElement?.RaisePointerMoveEvent(uiPointerEvent); // then generate enter/leave events if we passed from an element to another - if (currentTouchedElement != lastTouchedElement) - ThrowEnterAndLeavePointerEvents(currentTouchedElement, lastTouchedElement, uiPointerEvent); + if (currentInteractedElement != lastInteractedElement) + ThrowEnterAndPointerLeaveEvents(currentInteractedElement, lastInteractedElement, uiPointerEvent); break; case PointerEventType.Canceled: // generate enter/leave events if we passed from an element to another without move events - if (currentTouchedElement != lastTouchedElement) - ThrowEnterAndLeavePointerEvents(currentTouchedElement, lastTouchedElement, uiPointerEvent); + if (currentInteractedElement != lastInteractedElement) + ThrowEnterAndPointerLeaveEvents(currentInteractedElement, lastInteractedElement, uiPointerEvent); // then raise leave event to all the hierarchy of the previously selected element. - var element = currentTouchedElement; + var element = currentInteractedElement; while (element != null) { if (element.IsPointerDown) @@ -271,8 +271,8 @@ private void UpdatePointerEvents(UIDocument uiDocument, ref Viewport viewport, r throw new ArgumentOutOfRangeException(); } - lastTouchPosition = currentTouchPosition; - uiDocument.LastTouchedElement = currentTouchedElement; + lastPointerPosition = currentPointerPosition; + uiDocument.LastInteractedElement = currentInteractedElement; uiDocument.LastIntersectionPoint = intersectionPoint; } } @@ -356,7 +356,7 @@ private UIElement FindCommonParent(UIElement element1, UIElement element2) return commonElement; } - private void ThrowEnterAndLeavePointerEvents(UIElement currentElement, UIElement previousElement, PointerEventArgs pointerArgs) + private void ThrowEnterAndPointerLeaveEvents(UIElement currentElement, UIElement previousElement, PointerEventArgs pointerArgs) { var commonElement = FindCommonParent(currentElement, previousElement); From 2e45fc98147363108bf41673d23d22d01a87bee2 Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Tue, 22 Oct 2024 14:06:13 -0700 Subject: [PATCH 17/20] Added clarifying comments to UIDocument related to its state. --- sources/engine/Stride.UI/UIDocument.cs | 14 +++++++++----- sources/engine/Stride.UI/UISystem.PointerInput.cs | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/sources/engine/Stride.UI/UIDocument.cs b/sources/engine/Stride.UI/UIDocument.cs index c7063f3710..258ab8bad7 100644 --- a/sources/engine/Stride.UI/UIDocument.cs +++ b/sources/engine/Stride.UI/UIDocument.cs @@ -77,22 +77,26 @@ public class UIDocument public bool IsFixedSize { get; set; } /// - /// Last registered position of the mouse + /// Last registered position of the pointer on the . /// - public Vector2 LastMousePosition { get; set; } + /// Ignores other s. + public Vector2 LastPointerPosition { get; set; } /// - /// Last element over which the mouse cursor was registered + /// Last element over which the pointer was registered on the . /// + /// Ignores other s. public UIElement LastPointerOverElement { get; set; } /// - /// Last element which received a pointer event + /// Last element which received a pointer event on the . /// + /// Ignores other s. public UIElement LastInteractedElement { get; set; } /// - /// The last point in UI virtual world space that the pointer was over an element. + /// The last point in UI virtual world space that the pointer was over an element on the .. /// + /// Ignores other s. public Vector3 LastIntersectionPoint { get; set; } } diff --git a/sources/engine/Stride.UI/UISystem.PointerInput.cs b/sources/engine/Stride.UI/UISystem.PointerInput.cs index 708384b0a4..96591e6ff5 100644 --- a/sources/engine/Stride.UI/UISystem.PointerInput.cs +++ b/sources/engine/Stride.UI/UISystem.PointerInput.cs @@ -290,7 +290,7 @@ private UIElement UpdatePointerOver(UIDocument uiDocument, ref Viewport viewport UIElement pointerOveredElement = lastPointerOverElement; // Determine currently overed element. - if (mousePosition != uiDocument.LastMousePosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) + if (mousePosition != uiDocument.LastPointerPosition || (lastPointerOverElement?.RequiresMouseOverUpdate ?? false)) { Ray uiRay; @@ -333,7 +333,7 @@ private UIElement UpdatePointerOver(UIDocument uiDocument, ref Viewport viewport // update cached values uiDocument.LastPointerOverElement = pointerOveredElement; - uiDocument.LastMousePosition = mousePosition; + uiDocument.LastPointerPosition = mousePosition; return pointerOveredElement; } From 88d202100f906a056f598236cf1f4b489505b1cc Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Tue, 22 Oct 2024 14:17:34 -0700 Subject: [PATCH 18/20] Fixed grammar in comment in PointerOverState --- sources/engine/Stride.UI/PointerOverState.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/engine/Stride.UI/PointerOverState.cs b/sources/engine/Stride.UI/PointerOverState.cs index db668fb37f..2f87418e5c 100644 --- a/sources/engine/Stride.UI/PointerOverState.cs +++ b/sources/engine/Stride.UI/PointerOverState.cs @@ -13,9 +13,9 @@ public enum PointerOverState /// The pointer is neither over the element nor one of its children. None, /// - /// The pointer is over one of children of the element. + /// The pointer is over one of the children of the element. /// - /// The pointer is over one of children of the element. + /// The pointer is over one of the children of the element. Child, /// /// The pointer is directly over the element. From 29ee52c164c8c225aa12927deb3e36930fa0cd3d Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Tue, 22 Oct 2024 15:32:50 -0700 Subject: [PATCH 19/20] Deleted now unused TouchAction enum Was used by TouchEventArgs. But that has been refactored to PointerEventArgs and now uses the action time directly from the pointer input event. --- sources/engine/Stride.UI/TouchAction.cs | 27 ------------------------- 1 file changed, 27 deletions(-) delete mode 100644 sources/engine/Stride.UI/TouchAction.cs diff --git a/sources/engine/Stride.UI/TouchAction.cs b/sources/engine/Stride.UI/TouchAction.cs deleted file mode 100644 index 1b7c0333c1..0000000000 --- a/sources/engine/Stride.UI/TouchAction.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -namespace Stride.UI -{ - /// - /// Describes the action of a specific touch point. - /// - public enum TouchAction - { - /// - /// The act of putting a finger onto the screen. - /// - /// The act of putting a finger onto the screen. - Down, - /// - /// The act of dragging a finger across the screen. - /// - /// The act of dragging a finger across the screen. - Move, - /// - /// The act of lifting a finger off of the screen. - /// - /// The act of lifting a finger off of the screen. - Up, - } -} From b9a74b7f67907593bfce8f05e02c6e9311acfddc Mon Sep 17 00:00:00 2001 From: MechWarrior99 Date: Tue, 22 Oct 2024 15:35:30 -0700 Subject: [PATCH 20/20] Removed duplicated property IsTouchedDown from Slider Its functionality is already covered by IsPointerDown defined in UIElement. Tested at runtime to ensure there is no discrepancy in the behavior when using IsPointerDown. --- sources/engine/Stride.UI/Controls/Slider.cs | 23 +-------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/sources/engine/Stride.UI/Controls/Slider.cs b/sources/engine/Stride.UI/Controls/Slider.cs index 112c57de60..9d053e6fdd 100644 --- a/sources/engine/Stride.UI/Controls/Slider.cs +++ b/sources/engine/Stride.UI/Controls/Slider.cs @@ -289,12 +289,6 @@ public Orientation Orientation } } - /// - /// Gets a value that indicates whether the is currently touched down. - /// - [DataMemberIgnore] - protected virtual bool IsTouchedDown { get; set; } - /// /// Snap the current to the closest tick. /// @@ -393,28 +387,13 @@ protected override void OnPointerPressed(PointerEventArgs args) base.OnPointerPressed(args); SetValueFromPosition(args.WorldPosition); - IsTouchedDown = true; - } - - protected override void OnPointerReleased(PointerEventArgs args) - { - base.OnPointerReleased(args); - - IsTouchedDown = false; - } - - protected override void OnPointerLeave(PointerEventArgs args) - { - base.OnPointerLeave(args); - - IsTouchedDown = false; } protected override void OnPointerMove(PointerEventArgs args) { base.OnPointerMove(args); - if (IsTouchedDown) + if (IsPointerDown) { SetValueFromPosition(args.WorldPosition); }