< Summary

Class:DCL.PointerEventsController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/PointerEventsController/PointerEventsController.cs
Covered lines:169
Uncovered lines:46
Coverable lines:215
Total lines:516
Line coverage:78.6% (169 of 215)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
PointerEventsController()0%110100%
Initialize()0%440100%
Update()0%21.321091.23%
EventObjectCanBeHovered(...)0%440100%
ResolveGenericRaycastHandlers(...)0%1101000%
UnhoverLastHoveredObject(...)0%660100%
Cleanup()0%4.044086.67%
RetrieveCamera()0%220100%
GetRayFromCamera()0%110100%
OnButtonEvent(...)0%14.669058.82%
ProcessButtonUp(...)0%5.295077.27%
ProcessButtonDown(...)0%13.9813082.05%
ReportGlobalPointerUpEvent(...)0%5.935066.67%
ReportGlobalPointerDownEvent(...)0%550100%
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;
 67424        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;
 67435        PointerEventData uiGraphicRaycastPointerEventData = new PointerEventData(null);
 67436        List<RaycastResult> uiGraphicRaycastResults = new List<RaycastResult>();
 37        GraphicRaycaster uiGraphicRaycaster;
 38
 39        public void Initialize()
 40        {
 2022041            for (int i = 0; i < Enum.GetValues(typeof(WebInterface.ACTION_BUTTON)).Length; i++)
 42            {
 943643                var buttonId = (WebInterface.ACTION_BUTTON)i;
 44
 943645                if (buttonId == WebInterface.ACTION_BUTTON.ANY)
 46                    continue;
 47
 876248                InputController_Legacy.i.AddListener(buttonId, OnButtonEvent);
 49            }
 50
 67451            hoverController = InteractionHoverCanvasController.i;
 52
 67453            if (CursorController.i != null)
 54            {
 59055                OnPointerHoverStarts += CursorController.i.SetHoverCursor;
 59056                OnPointerHoverEnds += CursorController.i.SetNormalCursor;
 57            }
 58
 67459            RetrieveCamera();
 60
 67461            Environment.i.platform.updateEventHandler.AddListener(IUpdateEventHandler.EventType.Update, Update);
 67462        }
 63
 64        private IRaycastPointerClickHandler clickHandler;
 65
 66        public void Update()
 67        {
 1203568            if ( charCamera == null )
 287869                RetrieveCamera();
 70
 1203571            if (!CommonScriptableObjects.rendererState.Get() || charCamera == null)
 285372                return;
 73
 918274            IWorldState worldState = Environment.i.world.state;
 75
 76            // We use Physics.Raycast() instead of our raycastHandler.Raycast() as that one is slower, sometimes 2x, bec
 918277            bool didHit = Physics.Raycast(GetRayFromCamera(), out hitInfo, Mathf.Infinity, PhysicsLayers.physicsCastLaye
 918278            bool uiIsBlocking = false;
 918279            string currentSceneId = worldState.currentSceneId;
 80
 918281            bool validCurrentSceneId = !string.IsNullOrEmpty(currentSceneId);
 918282            bool validCurrentScene = validCurrentSceneId && worldState.loadedScenes.ContainsKey(currentSceneId);
 83
 84            // NOTE: in case of a single scene loaded (preview or builder) sceneId is set to null when stepping outside
 918285            if (didHit && validCurrentSceneId && validCurrentScene)
 86            {
 38487                UIScreenSpace currentUIScreenSpace = worldState.loadedScenes[currentSceneId].GetSharedComponent<UIScreen
 38488                GraphicRaycaster raycaster = currentUIScreenSpace?.graphicRaycaster;
 89
 38490                if (raycaster)
 91                {
 692                    uiGraphicRaycastPointerEventData.position = new Vector2(Screen.width / 2, Screen.height / 2);
 693                    uiGraphicRaycastResults.Clear();
 694                    raycaster.Raycast(uiGraphicRaycastPointerEventData, uiGraphicRaycastResults);
 695                    uiIsBlocking = uiGraphicRaycastResults.Count > 0;
 96                }
 97            }
 98
 918299            if (!didHit || uiIsBlocking)
 100            {
 8800101                clickHandler = null;
 8800102                UnhoverLastHoveredObject(hoverController);
 8800103                return;
 104            }
 105
 382106            var raycastHandlerTarget = hitInfo.collider.GetComponent<IRaycastPointerHandler>();
 382107            if (raycastHandlerTarget != null)
 108            {
 0109                ResolveGenericRaycastHandlers(raycastHandlerTarget);
 0110                UnhoverLastHoveredObject(hoverController);
 0111                return;
 112            }
 113
 382114            if (CollidersManager.i.GetColliderInfo(hitInfo.collider, out ColliderInfo info))
 382115                newHoveredEvent = info.entity.gameObject.GetComponentInChildren<IPointerEvent>();
 116            else
 0117                newHoveredEvent = hitInfo.collider.GetComponentInChildren<IPointerEvent>();
 118
 382119            clickHandler = null;
 120
 382121            if (!EventObjectCanBeHovered(info, hitInfo.distance))
 122            {
 13123                UnhoverLastHoveredObject(hoverController);
 13124                return;
 125            }
 126
 369127            newHoveredGO = newHoveredEvent.GetTransform().gameObject;
 128
 369129            if (newHoveredGO != lastHoveredObject)
 130            {
 14131                UnhoverLastHoveredObject(hoverController);
 132
 14133                lastHoveredObject = newHoveredGO;
 14134                lastHoveredEventList = newHoveredGO.GetComponents<IPointerEvent>();
 14135                OnPointerHoverStarts?.Invoke();
 136            }
 137
 138            // OnPointerDown/OnClick and OnPointerUp should display their hover feedback at different moments
 369139            if (lastHoveredEventList != null && lastHoveredEventList.Length > 0)
 140            {
 1476141                for (int i = 0; i < lastHoveredEventList.Length; i++)
 142                {
 369143                    IPointerEvent e = lastHoveredEventList[i];
 144
 369145                    bool eventButtonIsPressed = InputController_Legacy.i.IsPressed(e.GetActionButton());
 146
 369147                    bool isClick = e.GetEventType() == PointerEventType.CLICK;
 369148                    bool isDown = e.GetEventType() == PointerEventType.DOWN;
 369149                    bool isUp = e.GetEventType() == PointerEventType.UP;
 150
 369151                    if (isUp && eventButtonIsPressed)
 0152                        e.SetHoverState(true);
 369153                    else if ((isDown || isClick) && !eventButtonIsPressed)
 367154                        e.SetHoverState(true);
 155                    else
 2156                        e.SetHoverState(false);
 157                }
 158            }
 159
 369160            newHoveredGO = null;
 369161            newHoveredEvent = null;
 369162        }
 163
 164        private bool EventObjectCanBeHovered(ColliderInfo colliderInfo, float distance)
 165        {
 382166            return newHoveredEvent != null &&
 167                   newHoveredEvent.IsAtHoverDistance(distance) &&
 168                   newHoveredEvent.IsVisible() &&
 169                   AreSameEntity(newHoveredEvent, colliderInfo);
 170        }
 171
 172        private void ResolveGenericRaycastHandlers(IRaycastPointerHandler raycastHandlerTarget)
 173        {
 0174            if (Utils.LockedThisFrame())
 0175                return;
 176
 0177            var mouseIsDown = Input.GetMouseButtonDown(0);
 0178            var mouseIsUp = Input.GetMouseButtonUp(0);
 179
 0180            if (raycastHandlerTarget is IRaycastPointerDownHandler down)
 181            {
 0182                if (mouseIsDown)
 0183                    down.OnPointerDown();
 184            }
 185
 0186            if (raycastHandlerTarget is IRaycastPointerUpHandler up)
 187            {
 0188                if (mouseIsUp)
 0189                    up.OnPointerUp();
 190            }
 191
 0192            if (raycastHandlerTarget is IRaycastPointerClickHandler click)
 193            {
 0194                if (mouseIsDown)
 0195                    clickHandler = click;
 196
 0197                if (mouseIsUp)
 198                {
 0199                    if (clickHandler == click)
 0200                        click.OnPointerClick();
 0201                    clickHandler = null;
 202                }
 203            }
 0204        }
 205
 206        void UnhoverLastHoveredObject(InteractionHoverCanvasController interactionHoverCanvasController)
 207        {
 8827208            if (lastHoveredObject == null)
 209            {
 8823210                if ( interactionHoverCanvasController != null )
 296211                    interactionHoverCanvasController.SetHoverState(false);
 212
 8823213                return;
 214            }
 215
 4216            OnPointerHoverEnds?.Invoke();
 217
 16218            for (int i = 0; i < lastHoveredEventList.Length; i++)
 219            {
 4220                if (lastHoveredEventList[i] == null)
 221                    continue;
 4222                lastHoveredEventList[i].SetHoverState(false);
 223            }
 224
 4225            lastHoveredEventList = null;
 4226            lastHoveredObject = null;
 4227        }
 228
 229        public void Cleanup()
 230        {
 20370231            for (int i = 0; i < Enum.GetValues(typeof(WebInterface.ACTION_BUTTON)).Length; i++)
 232            {
 9506233                var buttonId = (WebInterface.ACTION_BUTTON)i;
 234
 9506235                if (buttonId == WebInterface.ACTION_BUTTON.ANY)
 236                    continue;
 237
 8827238                InputController_Legacy.i.RemoveListener(buttonId, OnButtonEvent);
 239            }
 240
 679241            lastHoveredObject = null;
 679242            newHoveredGO = null;
 679243            newHoveredEvent = null;
 679244            lastHoveredEventList = null;
 245
 679246            if (CursorController.i != null)
 247            {
 0248                OnPointerHoverStarts -= CursorController.i.SetHoverCursor;
 0249                OnPointerHoverEnds -= CursorController.i.SetNormalCursor;
 250            }
 251
 679252            Environment.i.platform.updateEventHandler.RemoveListener(IUpdateEventHandler.EventType.Update, Update);
 679253        }
 254
 255        void RetrieveCamera()
 256        {
 3552257            if (charCamera == null)
 258            {
 3552259                charCamera = Camera.main;
 260            }
 3552261        }
 262
 9196263        public Ray GetRayFromCamera() { return charCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height /
 264
 265        void OnButtonEvent(WebInterface.ACTION_BUTTON buttonId, InputController_Legacy.EVENT evt, bool useRaycast, bool 
 266        {
 267            //TODO(Brian): We should remove this when we get a proper initialization layer
 14268            if (!EnvironmentSettings.RUNNING_TESTS)
 269            {
 0270                if (Utils.LockedThisFrame())
 0271                    return;
 272
 0273                if (!Utils.isCursorLocked || !renderingEnabled)
 0274                    return;
 275            }
 276
 14277            if (charCamera == null)
 278            {
 0279                RetrieveCamera();
 280
 0281                if (charCamera == null)
 0282                    return;
 283            }
 284
 14285            var pointerEventLayer = PhysicsLayers.physicsCastLayerMaskWithoutCharacter; //Ensure characterController is 
 14286            var globalLayer = pointerEventLayer & ~PhysicsLayers.physicsCastLayerMask;
 287
 14288            if (evt == InputController_Legacy.EVENT.BUTTON_DOWN)
 289            {
 11290                ProcessButtonDown(buttonId, useRaycast, enablePointerEvent, pointerEventLayer, globalLayer);
 11291            }
 3292            else if (evt == InputController_Legacy.EVENT.BUTTON_UP)
 293            {
 3294                ProcessButtonUp(buttonId, useRaycast, enablePointerEvent, pointerEventLayer, globalLayer);
 295            }
 3296        }
 297
 298        private void ProcessButtonUp(WebInterface.ACTION_BUTTON buttonId, bool useRaycast, bool enablePointerEvent, Laye
 299        {
 3300            IWorldState worldState = Environment.i.world.state;
 301
 3302            if (string.IsNullOrEmpty(worldState.currentSceneId))
 0303                return;
 304
 305            RaycastHitInfo raycastGlobalLayerHitInfo;
 3306            Ray ray = GetRayFromCamera();
 307
 308            // Raycast for global pointer events
 3309            RaycastResultInfo raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer,
 3310            raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 311
 3312            if (pointerUpEvent != null)
 313            {
 314                // Raycast for pointer event components
 3315                RaycastResultInfo raycastInfoPointerEventLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, po
 316
 3317                bool isOnClickComponentBlocked = IsBlockingOnClick(raycastInfoPointerEventLayer.hitInfo, raycastGlobalLa
 3318                bool isSameEntityThatWasPressed = AreCollidersFromSameEntity(raycastInfoPointerEventLayer.hitInfo, lastP
 319
 3320                if (!isOnClickComponentBlocked && isSameEntityThatWasPressed && enablePointerEvent)
 321                {
 3322                    pointerUpEvent.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 323                }
 324
 3325                pointerUpEvent = null;
 326            }
 327
 3328            ReportGlobalPointerUpEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, worldSta
 329
 330            // Raycast for global pointer events (for each PE scene)
 3331            List<string> currentPortableExperienceIds = WorldStateUtils.GetActivePortableExperienceIds();
 6332            for (int i = 0; i < currentPortableExperienceIds.Count; i++)
 333            {
 0334                raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer, worldState.lo
 0335                raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 336
 0337                ReportGlobalPointerUpEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, curr
 338            }
 3339        }
 340
 341        private void ProcessButtonDown(WebInterface.ACTION_BUTTON buttonId, bool useRaycast, bool enablePointerEvent, La
 342        {
 11343            IWorldState worldState = Environment.i.world.state;
 344
 11345            if (string.IsNullOrEmpty(worldState.currentSceneId))
 0346                return;
 347
 348            RaycastHitInfo raycastGlobalLayerHitInfo;
 11349            Ray ray = GetRayFromCamera();
 350
 351            // Raycast for pointer event components
 11352            RaycastResultInfo raycastInfoPointerEventLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, pointe
 353
 354            // Raycast for global pointer events
 11355            RaycastResultInfo raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer,
 11356            raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 357
 11358            bool isOnClickComponentBlocked = IsBlockingOnClick(raycastInfoPointerEventLayer.hitInfo, raycastGlobalLayerH
 359
 11360            if (!isOnClickComponentBlocked && raycastInfoPointerEventLayer.hitInfo.hit.collider)
 361            {
 10362                Collider collider = raycastInfoPointerEventLayer.hitInfo.hit.collider;
 363
 364                GameObject hitGameObject;
 365
 10366                if (CollidersManager.i.GetColliderInfo(collider, out ColliderInfo info))
 10367                    hitGameObject = info.entity.gameObject;
 368                else
 0369                    hitGameObject = collider.gameObject;
 370
 10371                var events = hitGameObject.GetComponentsInChildren<IPointerEvent>();
 372
 36373                for (var i = 0; i < events.Length; i++)
 374                {
 8375                    IPointerEvent e = events[i];
 8376                    bool areSameEntity = AreSameEntity(e, info);
 377
 8378                    switch (e.GetEventType())
 379                    {
 380                        case PointerEventType.CLICK:
 1381                            if (areSameEntity && enablePointerEvent)
 1382                                e.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 1383                            break;
 384                        case PointerEventType.DOWN:
 4385                            if (areSameEntity && enablePointerEvent)
 4386                                e.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 4387                            break;
 388                        case PointerEventType.UP:
 3389                            if (areSameEntity && enablePointerEvent)
 3390                                pointerUpEvent = e;
 391                            else
 0392                                pointerUpEvent = null;
 393                            break;
 394                    }
 395                }
 396
 10397                lastPointerDownEventHitInfo = raycastInfoPointerEventLayer.hitInfo;
 398            }
 399
 11400            ReportGlobalPointerDownEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, worldS
 401
 402            // Raycast for global pointer events (for each PE scene)
 11403            List<string> currentPortableExperienceIds = WorldStateUtils.GetActivePortableExperienceIds();
 22404            for (int i = 0; i < currentPortableExperienceIds.Count; i++)
 405            {
 0406                raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer, worldState.lo
 0407                raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 408
 0409                ReportGlobalPointerDownEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, cu
 410            }
 11411        }
 412
 413        private void ReportGlobalPointerUpEvent(
 414            WebInterface.ACTION_BUTTON buttonId,
 415            bool useRaycast,
 416            RaycastHitInfo raycastGlobalLayerHitInfo,
 417            RaycastResultInfo raycastInfoGlobalLayer,
 418            string sceneId)
 419        {
 3420            if (useRaycast && raycastGlobalLayerHitInfo.isValid)
 421            {
 3422                CollidersManager.i.GetColliderInfo(raycastGlobalLayerHitInfo.hit.collider, out ColliderInfo colliderInfo
 423
 3424                WebInterface.ReportGlobalPointerUpEvent(
 425                    buttonId,
 426                    raycastInfoGlobalLayer.ray,
 427                    raycastGlobalLayerHitInfo.hit.point,
 428                    raycastGlobalLayerHitInfo.hit.normal,
 429                    raycastGlobalLayerHitInfo.hit.distance,
 430                    sceneId,
 431                    colliderInfo.entity != null ? colliderInfo.entity.entityId : null,
 432                    colliderInfo.meshName,
 433                    isHitInfoValid: true);
 3434            }
 435            else
 436            {
 0437                WebInterface.ReportGlobalPointerUpEvent(buttonId, raycastInfoGlobalLayer.ray, Vector3.zero, Vector3.zero
 438            }
 0439        }
 440
 441        private void ReportGlobalPointerDownEvent(
 442            WebInterface.ACTION_BUTTON buttonId,
 443            bool useRaycast,
 444            RaycastHitInfo raycastGlobalLayerHitInfo,
 445            RaycastResultInfo raycastInfoGlobalLayer,
 446            string sceneId)
 447        {
 11448            if (useRaycast && raycastGlobalLayerHitInfo.isValid)
 449            {
 10450                CollidersManager.i.GetColliderInfo(raycastGlobalLayerHitInfo.hit.collider, out ColliderInfo colliderInfo
 451
 10452                WebInterface.ReportGlobalPointerDownEvent(
 453                    buttonId,
 454                    raycastInfoGlobalLayer.ray,
 455                    raycastGlobalLayerHitInfo.hit.point,
 456                    raycastGlobalLayerHitInfo.hit.normal,
 457                    raycastGlobalLayerHitInfo.hit.distance,
 458                    sceneId,
 459                    colliderInfo.entity != null ? colliderInfo.entity.entityId : null,
 460                    colliderInfo.meshName,
 461                    isHitInfoValid: true);
 10462            }
 463            else
 464            {
 1465                WebInterface.ReportGlobalPointerDownEvent(buttonId, raycastInfoGlobalLayer.ray, Vector3.zero, Vector3.ze
 466            }
 1467        }
 468
 378469        bool AreSameEntity(IPointerEvent pointerEvent, ColliderInfo colliderInfo) { return pointerEvent != null && colli
 470
 471        bool IsBlockingOnClick(RaycastHitInfo targetOnClickHit, RaycastHitInfo potentialBlockerHit)
 472        {
 14473            return
 474                potentialBlockerHit.hit.collider != null // Does a potential blocker hit exist?
 475                && targetOnClickHit.hit.collider != null // Was a target entity with a pointer event component hit?
 476                && potentialBlockerHit.hit.distance <= targetOnClickHit.hit.distance // Is potential blocker nearer than
 477                && !AreCollidersFromSameEntity(potentialBlockerHit, targetOnClickHit); // Does potential blocker belong 
 478        }
 479
 480        bool EntityHasPointerEvent(IDCLEntity entity)
 481        {
 32482            return entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_CALLBACK) ||
 483                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_UP) ||
 484                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_DOWN) ||
 485                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_CLICK);
 486        }
 487
 488        bool AreCollidersFromSameEntity(RaycastHitInfo hitInfoA, RaycastHitInfo hitInfoB)
 489        {
 16490            CollidersManager.i.GetColliderInfo(hitInfoA.hit.collider, out ColliderInfo colliderInfoA);
 16491            CollidersManager.i.GetColliderInfo(hitInfoB.hit.collider, out ColliderInfo colliderInfoB);
 492
 16493            var entityA = colliderInfoA.entity;
 16494            var entityB = colliderInfoB.entity;
 495
 16496            bool entityAHasEvent = entityA != null && EntityHasPointerEvent(entityA);
 16497            bool entityBHasEvent = entityB != null && EntityHasPointerEvent(entityB);
 498
 499            // If both entities has OnClick/PointerEvent component
 16500            if (entityAHasEvent && entityBHasEvent)
 501            {
 14502                return entityA == entityB;
 503            }
 504            // If only one of them has OnClick/PointerEvent component
 2505            else if (entityAHasEvent ^ entityBHasEvent)
 506            {
 0507                return false;
 508            }
 509            // None of them has OnClick/PointerEvent component
 510            else
 511            {
 2512                return colliderInfoA.entity == colliderInfoB.entity;
 513            }
 514        }
 515    }
 516}