< Summary

Class:DCL.PointerEventsController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/PointerEventsController/PointerEventsController.cs
Covered lines:160
Uncovered lines:44
Coverable lines:204
Total lines:487
Line coverage:78.4% (160 of 204)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
PointerEventsController()0%110100%
Initialize()0%220100%
Update()0%20.320090.91%
EventObjectCanBeHovered(...)0%440100%
ResolveGenericRaycastHandlers(...)0%1101000%
UnhoverLastHoveredObject(...)0%550100%
Cleanup()0%220100%
RetrieveCamera()0%220100%
GetRayFromCamera()0%110100%
OnButtonEvent(...)0%14.669058.82%
ProcessButtonUp(...)0%4.134080%
ProcessButtonDown(...)0%12.6112083.78%
ReportGlobalPointerUpEvent(...)0%5.935066.67%
ReportGlobalPointerDownEvent(...)0%5.935066.67%
AreSameEntity(...)0%330100%
IsBlockingOnClick(...)0%440100%
EntityHasPointerEvent(...)0%440100%
AreCollidersFromSameEntity(...)0%5.025090.91%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/PointerEventsController/PointerEventsController.cs

#LineLine coverage
 1using System;
 2using DCL.Components;
 3using DCL.Configuration;
 4using DCL.Helpers;
 5using DCL.Interface;
 6using UnityEngine;
 7using UnityEngine.UI;
 8using UnityEngine.EventSystems;
 9using System.Collections.Generic;
 10using DCL.Models;
 11using Ray = UnityEngine.Ray;
 12
 13namespace DCL
 14{
 15    public class PointerEventsController : IPointerEventsController
 16    {
 017        private static bool renderingEnabled => CommonScriptableObjects.rendererState.Get();
 18        public System.Action OnPointerHoverStarts;
 19        public System.Action OnPointerHoverEnds;
 20
 21        InteractionHoverCanvasController hoverController;
 22        RaycastHitInfo lastPointerDownEventHitInfo;
 23        IPointerEvent pointerUpEvent;
 66624        IRaycastHandler raycastHandler = new RaycastHandler();
 25
 26        Camera charCamera;
 27
 28        GameObject lastHoveredObject = null;
 29        GameObject newHoveredGO = null;
 30
 31        IPointerEvent newHoveredEvent = null;
 32        IPointerEvent[] lastHoveredEventList = null;
 33
 34        RaycastHit hitInfo;
 66635        PointerEventData uiGraphicRaycastPointerEventData = new PointerEventData(null);
 66636        List<RaycastResult> uiGraphicRaycastResults = new List<RaycastResult>();
 37        GraphicRaycaster uiGraphicRaycaster;
 38
 39        public void Initialize()
 40        {
 66641            InputController_Legacy.i.AddListener(WebInterface.ACTION_BUTTON.POINTER, OnButtonEvent);
 66642            InputController_Legacy.i.AddListener(WebInterface.ACTION_BUTTON.PRIMARY, OnButtonEvent);
 66643            InputController_Legacy.i.AddListener(WebInterface.ACTION_BUTTON.SECONDARY, OnButtonEvent);
 44
 66645            hoverController = InteractionHoverCanvasController.i;
 46
 66647            if (CursorController.i != null)
 48            {
 66649                OnPointerHoverStarts += CursorController.i.SetHoverCursor;
 66650                OnPointerHoverEnds += CursorController.i.SetNormalCursor;
 51            }
 52
 66653            RetrieveCamera();
 66654        }
 55
 56        private IRaycastPointerClickHandler clickHandler;
 57
 58        public void Update()
 59        {
 1890360            if (!CommonScriptableObjects.rendererState.Get() || charCamera == null)
 548661                return;
 62
 1341763            IWorldState worldState = Environment.i.world.state;
 64
 65            // We use Physics.Raycast() instead of our raycastHandler.Raycast() as that one is slower, sometimes 2x, bec
 1341766            bool didHit = Physics.Raycast(GetRayFromCamera(), out hitInfo, Mathf.Infinity, PhysicsLayers.physicsCastLaye
 1341767            bool uiIsBlocking = false;
 1341768            string currentSceneId = worldState.currentSceneId;
 69
 1341770            bool validCurrentSceneId = !string.IsNullOrEmpty(currentSceneId);
 1341771            bool validCurrentScene = validCurrentSceneId && worldState.loadedScenes.ContainsKey(currentSceneId);
 72
 73            // NOTE: in case of a single scene loaded (preview or builder) sceneId is set to null when stepping outside
 1341774            if (didHit && validCurrentSceneId && validCurrentScene)
 75            {
 4976                UIScreenSpace currentUIScreenSpace = worldState.loadedScenes[currentSceneId].GetSharedComponent<UIScreen
 4977                GraphicRaycaster raycaster = currentUIScreenSpace?.graphicRaycaster;
 78
 4979                if (raycaster)
 80                {
 881                    uiGraphicRaycastPointerEventData.position = new Vector2(Screen.width / 2, Screen.height / 2);
 882                    uiGraphicRaycastResults.Clear();
 883                    raycaster.Raycast(uiGraphicRaycastPointerEventData, uiGraphicRaycastResults);
 884                    uiIsBlocking = uiGraphicRaycastResults.Count > 0;
 85                }
 86            }
 87
 1341788            if (!didHit || uiIsBlocking)
 89            {
 1336490                clickHandler = null;
 1336491                UnhoverLastHoveredObject(hoverController);
 1336492                return;
 93            }
 94
 5395            var raycastHandlerTarget = hitInfo.collider.GetComponent<IRaycastPointerHandler>();
 5396            if (raycastHandlerTarget != null)
 97            {
 098                ResolveGenericRaycastHandlers(raycastHandlerTarget);
 099                UnhoverLastHoveredObject(hoverController);
 0100                return;
 101            }
 102
 53103            if (CollidersManager.i.GetColliderInfo(hitInfo.collider, out ColliderInfo info))
 53104                newHoveredEvent = info.entity.gameObject.GetComponentInChildren<IPointerEvent>();
 105            else
 0106                newHoveredEvent = hitInfo.collider.GetComponentInChildren<IPointerEvent>();
 107
 53108            clickHandler = null;
 109
 53110            if (!EventObjectCanBeHovered(info, hitInfo.distance))
 111            {
 15112                UnhoverLastHoveredObject(hoverController);
 15113                return;
 114            }
 115
 38116            newHoveredGO = newHoveredEvent.GetTransform().gameObject;
 117
 38118            if (newHoveredGO != lastHoveredObject)
 119            {
 15120                UnhoverLastHoveredObject(hoverController);
 121
 15122                lastHoveredObject = newHoveredGO;
 15123                lastHoveredEventList = newHoveredGO.GetComponents<IPointerEvent>();
 15124                OnPointerHoverStarts?.Invoke();
 125            }
 126
 127            // OnPointerDown/OnClick and OnPointerUp should display their hover feedback at different moments
 38128            if (lastHoveredEventList != null && lastHoveredEventList.Length > 0)
 129            {
 152130                for (int i = 0; i < lastHoveredEventList.Length; i++)
 131                {
 38132                    IPointerEvent e = lastHoveredEventList[i];
 133
 38134                    bool eventButtonIsPressed = InputController_Legacy.i.IsPressed(e.GetActionButton());
 135
 38136                    bool isClick = e.GetEventType() == PointerEventType.CLICK;
 38137                    bool isDown = e.GetEventType() == PointerEventType.DOWN;
 38138                    bool isUp = e.GetEventType() == PointerEventType.UP;
 139
 38140                    if (isUp && eventButtonIsPressed)
 0141                        e.SetHoverState(true);
 38142                    else if ((isDown || isClick) && !eventButtonIsPressed)
 35143                        e.SetHoverState(true);
 144                    else
 3145                        e.SetHoverState(false);
 146                }
 147            }
 148
 38149            newHoveredGO = null;
 38150            newHoveredEvent = null;
 38151        }
 152
 153        private bool EventObjectCanBeHovered(ColliderInfo colliderInfo, float distance)
 154        {
 53155            return newHoveredEvent != null &&
 156                   newHoveredEvent.IsAtHoverDistance(distance) &&
 157                   newHoveredEvent.IsVisible() &&
 158                   AreSameEntity(newHoveredEvent, colliderInfo);
 159        }
 160
 161        private void ResolveGenericRaycastHandlers(IRaycastPointerHandler raycastHandlerTarget)
 162        {
 0163            if (Utils.LockedThisFrame())
 0164                return;
 165
 0166            var mouseIsDown = Input.GetMouseButtonDown(0);
 0167            var mouseIsUp = Input.GetMouseButtonUp(0);
 168
 0169            if (raycastHandlerTarget is IRaycastPointerDownHandler down)
 170            {
 0171                if (mouseIsDown)
 0172                    down.OnPointerDown();
 173            }
 174
 0175            if (raycastHandlerTarget is IRaycastPointerUpHandler up)
 176            {
 0177                if (mouseIsUp)
 0178                    up.OnPointerUp();
 179            }
 180
 0181            if (raycastHandlerTarget is IRaycastPointerClickHandler click)
 182            {
 0183                if (mouseIsDown)
 0184                    clickHandler = click;
 185
 0186                if (mouseIsUp)
 187                {
 0188                    if (clickHandler == click)
 0189                        click.OnPointerClick();
 0190                    clickHandler = null;
 191                }
 192            }
 0193        }
 194
 195        void UnhoverLastHoveredObject(InteractionHoverCanvasController interactionHoverCanvasController)
 196        {
 13394197            if (lastHoveredObject == null)
 198            {
 13389199                interactionHoverCanvasController.SetHoverState(false);
 13389200                return;
 201            }
 202
 5203            OnPointerHoverEnds?.Invoke();
 204
 20205            for (int i = 0; i < lastHoveredEventList.Length; i++)
 206            {
 5207                if (lastHoveredEventList[i] == null)
 208                    continue;
 5209                lastHoveredEventList[i].SetHoverState(false);
 210            }
 211
 5212            lastHoveredEventList = null;
 5213            lastHoveredObject = null;
 5214        }
 215
 216        public void Cleanup()
 217        {
 687218            InputController_Legacy.i.RemoveListener(WebInterface.ACTION_BUTTON.POINTER, OnButtonEvent);
 687219            InputController_Legacy.i.RemoveListener(WebInterface.ACTION_BUTTON.PRIMARY, OnButtonEvent);
 687220            InputController_Legacy.i.RemoveListener(WebInterface.ACTION_BUTTON.SECONDARY, OnButtonEvent);
 221
 687222            lastHoveredObject = null;
 687223            newHoveredGO = null;
 687224            newHoveredEvent = null;
 687225            lastHoveredEventList = null;
 226
 687227            if (CursorController.i != null)
 228            {
 666229                OnPointerHoverStarts -= CursorController.i.SetHoverCursor;
 666230                OnPointerHoverEnds -= CursorController.i.SetNormalCursor;
 231            }
 687232        }
 233
 234        void RetrieveCamera()
 235        {
 666236            if (charCamera == null)
 237            {
 666238                charCamera = Camera.main;
 239            }
 666240        }
 241
 13431242        public Ray GetRayFromCamera() { return charCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height /
 243
 244        void OnButtonEvent(WebInterface.ACTION_BUTTON buttonId, InputController_Legacy.EVENT evt, bool useRaycast)
 245        {
 246            //TODO(Brian): We should remove this when we get a proper initialization layer
 14247            if (!EnvironmentSettings.RUNNING_TESTS)
 248            {
 0249                if (Utils.LockedThisFrame())
 0250                    return;
 251
 0252                if (!Utils.isCursorLocked || !renderingEnabled)
 0253                    return;
 254            }
 255
 14256            if (charCamera == null)
 257            {
 0258                RetrieveCamera();
 259
 0260                if (charCamera == null)
 0261                    return;
 262            }
 263
 14264            var pointerEventLayer = PhysicsLayers.physicsCastLayerMaskWithoutCharacter; //Ensure characterController is 
 14265            var globalLayer = pointerEventLayer & ~PhysicsLayers.physicsCastLayerMask;
 266
 14267            if (evt == InputController_Legacy.EVENT.BUTTON_DOWN)
 268            {
 11269                ProcessButtonDown(buttonId, useRaycast, pointerEventLayer, globalLayer);
 11270            }
 3271            else if (evt == InputController_Legacy.EVENT.BUTTON_UP)
 272            {
 3273                ProcessButtonUp(buttonId, useRaycast, pointerEventLayer, globalLayer);
 274            }
 3275        }
 276
 277        private void ProcessButtonUp(WebInterface.ACTION_BUTTON buttonId, bool useRaycast, LayerMask pointerEventLayer, 
 278        {
 3279            IWorldState worldState = Environment.i.world.state;
 280            RaycastHitInfo raycastGlobalLayerHitInfo;
 3281            Ray ray = GetRayFromCamera();
 282
 283            // Raycast for global pointer events
 3284            RaycastResultInfo raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer,
 3285            raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 286
 3287            if (pointerUpEvent != null)
 288            {
 289                // Raycast for pointer event components
 3290                RaycastResultInfo raycastInfoPointerEventLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, po
 291
 3292                bool isOnClickComponentBlocked = IsBlockingOnClick(raycastInfoPointerEventLayer.hitInfo, raycastGlobalLa
 3293                bool isSameEntityThatWasPressed = AreCollidersFromSameEntity(raycastInfoPointerEventLayer.hitInfo, lastP
 294
 3295                if (!isOnClickComponentBlocked && isSameEntityThatWasPressed)
 296                {
 3297                    pointerUpEvent.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 298                }
 299
 3300                pointerUpEvent = null;
 301            }
 302
 3303            ReportGlobalPointerUpEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, worldSta
 304
 305            // Raycast for global pointer events (for each PE scene)
 3306            List<string> currentPortableExperienceIds = WorldStateUtils.GetActivePortableExperienceIds();
 6307            for (int i = 0; i < currentPortableExperienceIds.Count; i++)
 308            {
 0309                raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer, worldState.lo
 0310                raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 311
 0312                ReportGlobalPointerUpEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, curr
 313            }
 3314        }
 315
 316        private void ProcessButtonDown(WebInterface.ACTION_BUTTON buttonId, bool useRaycast, LayerMask pointerEventLayer
 317        {
 11318            IWorldState worldState = Environment.i.world.state;
 319            RaycastHitInfo raycastGlobalLayerHitInfo;
 11320            Ray ray = GetRayFromCamera();
 321
 322            // Raycast for pointer event components
 11323            RaycastResultInfo raycastInfoPointerEventLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, pointe
 324
 325            // Raycast for global pointer events
 11326            RaycastResultInfo raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer,
 11327            raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 328
 11329            bool isOnClickComponentBlocked = IsBlockingOnClick(raycastInfoPointerEventLayer.hitInfo, raycastGlobalLayerH
 330
 11331            if (!isOnClickComponentBlocked && raycastInfoPointerEventLayer.hitInfo.hit.collider)
 332            {
 11333                Collider collider = raycastInfoPointerEventLayer.hitInfo.hit.collider;
 334
 335                GameObject hitGameObject;
 336
 11337                if (CollidersManager.i.GetColliderInfo(collider, out ColliderInfo info))
 11338                    hitGameObject = info.entity.gameObject;
 339                else
 0340                    hitGameObject = collider.gameObject;
 341
 11342                var events = hitGameObject.GetComponentsInChildren<IPointerEvent>();
 343
 40344                for (var i = 0; i < events.Length; i++)
 345                {
 9346                    IPointerEvent e = events[i];
 9347                    bool areSameEntity = AreSameEntity(e, info);
 348
 9349                    switch (e.GetEventType())
 350                    {
 351                        case PointerEventType.CLICK:
 1352                            if (areSameEntity)
 1353                                e.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 1354                            break;
 355                        case PointerEventType.DOWN:
 5356                            if (areSameEntity)
 4357                                e.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 4358                            break;
 359                        case PointerEventType.UP:
 3360                            if (areSameEntity)
 3361                                pointerUpEvent = e;
 362                            else
 0363                                pointerUpEvent = null;
 364                            break;
 365                    }
 366                }
 367
 11368                lastPointerDownEventHitInfo = raycastInfoPointerEventLayer.hitInfo;
 369            }
 370
 11371            ReportGlobalPointerDownEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, worldS
 372
 373            // Raycast for global pointer events (for each PE scene)
 11374            List<string> currentPortableExperienceIds = WorldStateUtils.GetActivePortableExperienceIds();
 22375            for (int i = 0; i < currentPortableExperienceIds.Count; i++)
 376            {
 0377                raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer, worldState.lo
 0378                raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 379
 0380                ReportGlobalPointerDownEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, cu
 381            }
 11382        }
 383
 384        private void ReportGlobalPointerUpEvent(
 385            WebInterface.ACTION_BUTTON buttonId,
 386            bool useRaycast,
 387            RaycastHitInfo raycastGlobalLayerHitInfo,
 388            RaycastResultInfo raycastInfoGlobalLayer,
 389            string sceneId)
 390        {
 3391            if (useRaycast && raycastGlobalLayerHitInfo.isValid)
 392            {
 3393                CollidersManager.i.GetColliderInfo(raycastGlobalLayerHitInfo.hit.collider, out ColliderInfo colliderInfo
 394
 3395                WebInterface.ReportGlobalPointerUpEvent(
 396                    buttonId,
 397                    raycastInfoGlobalLayer.ray,
 398                    raycastGlobalLayerHitInfo.hit.point,
 399                    raycastGlobalLayerHitInfo.hit.normal,
 400                    raycastGlobalLayerHitInfo.hit.distance,
 401                    sceneId,
 402                    colliderInfo.entity != null ? colliderInfo.entity.entityId : null,
 403                    colliderInfo.meshName,
 404                    isHitInfoValid: true);
 3405            }
 406            else
 407            {
 0408                WebInterface.ReportGlobalPointerUpEvent(buttonId, raycastInfoGlobalLayer.ray, Vector3.zero, Vector3.zero
 409            }
 0410        }
 411
 412        private void ReportGlobalPointerDownEvent(
 413            WebInterface.ACTION_BUTTON buttonId,
 414            bool useRaycast,
 415            RaycastHitInfo raycastGlobalLayerHitInfo,
 416            RaycastResultInfo raycastInfoGlobalLayer,
 417            string sceneId)
 418        {
 11419            if (useRaycast && raycastGlobalLayerHitInfo.isValid)
 420            {
 11421                CollidersManager.i.GetColliderInfo(raycastGlobalLayerHitInfo.hit.collider, out ColliderInfo colliderInfo
 422
 11423                WebInterface.ReportGlobalPointerDownEvent(
 424                    buttonId,
 425                    raycastInfoGlobalLayer.ray,
 426                    raycastGlobalLayerHitInfo.hit.point,
 427                    raycastGlobalLayerHitInfo.hit.normal,
 428                    raycastGlobalLayerHitInfo.hit.distance,
 429                    sceneId,
 430                    colliderInfo.entity != null ? colliderInfo.entity.entityId : null,
 431                    colliderInfo.meshName,
 432                    isHitInfoValid: true);
 11433            }
 434            else
 435            {
 0436                WebInterface.ReportGlobalPointerDownEvent(buttonId, raycastInfoGlobalLayer.ray, Vector3.zero, Vector3.ze
 437            }
 0438        }
 439
 48440        bool AreSameEntity(IPointerEvent pointerEvent, ColliderInfo colliderInfo) { return pointerEvent != null && colli
 441
 442        bool IsBlockingOnClick(RaycastHitInfo targetOnClickHit, RaycastHitInfo potentialBlockerHit)
 443        {
 14444            return
 445                potentialBlockerHit.hit.collider != null // Does a potential blocker hit exist?
 446                && targetOnClickHit.hit.collider != null // Was a target entity with a pointer event component hit?
 447                && potentialBlockerHit.hit.distance <= targetOnClickHit.hit.distance // Is potential blocker nearer than
 448                && !AreCollidersFromSameEntity(potentialBlockerHit, targetOnClickHit); // Does potential blocker belong 
 449        }
 450
 451        bool EntityHasPointerEvent(IDCLEntity entity)
 452        {
 34453            return entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_CALLBACK) ||
 454                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_UP) ||
 455                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_DOWN) ||
 456                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_CLICK);
 457        }
 458
 459        bool AreCollidersFromSameEntity(RaycastHitInfo hitInfoA, RaycastHitInfo hitInfoB)
 460        {
 17461            CollidersManager.i.GetColliderInfo(hitInfoA.hit.collider, out ColliderInfo colliderInfoA);
 17462            CollidersManager.i.GetColliderInfo(hitInfoB.hit.collider, out ColliderInfo colliderInfoB);
 463
 17464            var entityA = colliderInfoA.entity;
 17465            var entityB = colliderInfoB.entity;
 466
 17467            bool entityAHasEvent = entityA != null && EntityHasPointerEvent(entityA);
 17468            bool entityBHasEvent = entityB != null && EntityHasPointerEvent(entityB);
 469
 470            // If both entities has OnClick/PointerEvent component
 17471            if (entityAHasEvent && entityBHasEvent)
 472            {
 14473                return entityA == entityB;
 474            }
 475            // If only one of them has OnClick/PointerEvent component
 3476            else if (entityAHasEvent ^ entityBHasEvent)
 477            {
 0478                return false;
 479            }
 480            // None of them has OnClick/PointerEvent component
 481            else
 482            {
 3483                return colliderInfoA.entity == colliderInfoB.entity;
 484            }
 485        }
 486    }
 487}