< Summary

Class:DCL.PointerEventsController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/PointerEventsController/PointerEventsController.cs
Covered lines:164
Uncovered lines:46
Coverable lines:210
Total lines:507
Line coverage:78% (164 of 210)
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%20.320090.91%
EventObjectCanBeHovered(...)0%440100%
ResolveGenericRaycastHandlers(...)0%1101000%
UnhoverLastHoveredObject(...)0%550100%
Cleanup()0%440100%
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%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;
 69524        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;
 69535        PointerEventData uiGraphicRaycastPointerEventData = new PointerEventData(null);
 69536        List<RaycastResult> uiGraphicRaycastResults = new List<RaycastResult>();
 37        GraphicRaycaster uiGraphicRaycaster;
 38
 39        public void Initialize()
 40        {
 2085041            for (int i = 0; i < Enum.GetValues(typeof(WebInterface.ACTION_BUTTON)).Length; i++)
 42            {
 973043                var buttonId = (WebInterface.ACTION_BUTTON)i;
 44
 973045                if (buttonId == WebInterface.ACTION_BUTTON.ANY)
 46                    continue;
 47
 903548                InputController_Legacy.i.AddListener(buttonId, OnButtonEvent);
 49            }
 50
 69551            hoverController = InteractionHoverCanvasController.i;
 52
 69553            if (CursorController.i != null)
 54            {
 69555                OnPointerHoverStarts += CursorController.i.SetHoverCursor;
 69556                OnPointerHoverEnds += CursorController.i.SetNormalCursor;
 57            }
 58
 69559            RetrieveCamera();
 69560        }
 61
 62        private IRaycastPointerClickHandler clickHandler;
 63
 64        public void Update()
 65        {
 1924666            if (!CommonScriptableObjects.rendererState.Get() || charCamera == null)
 550067                return;
 68
 1374669            IWorldState worldState = Environment.i.world.state;
 70
 71            // We use Physics.Raycast() instead of our raycastHandler.Raycast() as that one is slower, sometimes 2x, bec
 1374672            bool didHit = Physics.Raycast(GetRayFromCamera(), out hitInfo, Mathf.Infinity, PhysicsLayers.physicsCastLaye
 1374673            bool uiIsBlocking = false;
 1374674            string currentSceneId = worldState.currentSceneId;
 75
 1374676            bool validCurrentSceneId = !string.IsNullOrEmpty(currentSceneId);
 1374677            bool validCurrentScene = validCurrentSceneId && worldState.loadedScenes.ContainsKey(currentSceneId);
 78
 79            // NOTE: in case of a single scene loaded (preview or builder) sceneId is set to null when stepping outside
 1374680            if (didHit && validCurrentSceneId && validCurrentScene)
 81            {
 5882                UIScreenSpace currentUIScreenSpace = worldState.loadedScenes[currentSceneId].GetSharedComponent<UIScreen
 5883                GraphicRaycaster raycaster = currentUIScreenSpace?.graphicRaycaster;
 84
 5885                if (raycaster)
 86                {
 887                    uiGraphicRaycastPointerEventData.position = new Vector2(Screen.width / 2, Screen.height / 2);
 888                    uiGraphicRaycastResults.Clear();
 889                    raycaster.Raycast(uiGraphicRaycastPointerEventData, uiGraphicRaycastResults);
 890                    uiIsBlocking = uiGraphicRaycastResults.Count > 0;
 91                }
 92            }
 93
 1374694            if (!didHit || uiIsBlocking)
 95            {
 1369196                clickHandler = null;
 1369197                UnhoverLastHoveredObject(hoverController);
 1369198                return;
 99            }
 100
 55101            var raycastHandlerTarget = hitInfo.collider.GetComponent<IRaycastPointerHandler>();
 55102            if (raycastHandlerTarget != null)
 103            {
 0104                ResolveGenericRaycastHandlers(raycastHandlerTarget);
 0105                UnhoverLastHoveredObject(hoverController);
 0106                return;
 107            }
 108
 55109            if (CollidersManager.i.GetColliderInfo(hitInfo.collider, out ColliderInfo info))
 55110                newHoveredEvent = info.entity.gameObject.GetComponentInChildren<IPointerEvent>();
 111            else
 0112                newHoveredEvent = hitInfo.collider.GetComponentInChildren<IPointerEvent>();
 113
 55114            clickHandler = null;
 115
 55116            if (!EventObjectCanBeHovered(info, hitInfo.distance))
 117            {
 15118                UnhoverLastHoveredObject(hoverController);
 15119                return;
 120            }
 121
 40122            newHoveredGO = newHoveredEvent.GetTransform().gameObject;
 123
 40124            if (newHoveredGO != lastHoveredObject)
 125            {
 14126                UnhoverLastHoveredObject(hoverController);
 127
 14128                lastHoveredObject = newHoveredGO;
 14129                lastHoveredEventList = newHoveredGO.GetComponents<IPointerEvent>();
 14130                OnPointerHoverStarts?.Invoke();
 131            }
 132
 133            // OnPointerDown/OnClick and OnPointerUp should display their hover feedback at different moments
 40134            if (lastHoveredEventList != null && lastHoveredEventList.Length > 0)
 135            {
 160136                for (int i = 0; i < lastHoveredEventList.Length; i++)
 137                {
 40138                    IPointerEvent e = lastHoveredEventList[i];
 139
 40140                    bool eventButtonIsPressed = InputController_Legacy.i.IsPressed(e.GetActionButton());
 141
 40142                    bool isClick = e.GetEventType() == PointerEventType.CLICK;
 40143                    bool isDown = e.GetEventType() == PointerEventType.DOWN;
 40144                    bool isUp = e.GetEventType() == PointerEventType.UP;
 145
 40146                    if (isUp && eventButtonIsPressed)
 0147                        e.SetHoverState(true);
 40148                    else if ((isDown || isClick) && !eventButtonIsPressed)
 37149                        e.SetHoverState(true);
 150                    else
 3151                        e.SetHoverState(false);
 152                }
 153            }
 154
 40155            newHoveredGO = null;
 40156            newHoveredEvent = null;
 40157        }
 158
 159        private bool EventObjectCanBeHovered(ColliderInfo colliderInfo, float distance)
 160        {
 55161            return newHoveredEvent != null &&
 162                   newHoveredEvent.IsAtHoverDistance(distance) &&
 163                   newHoveredEvent.IsVisible() &&
 164                   AreSameEntity(newHoveredEvent, colliderInfo);
 165        }
 166
 167        private void ResolveGenericRaycastHandlers(IRaycastPointerHandler raycastHandlerTarget)
 168        {
 0169            if (Utils.LockedThisFrame())
 0170                return;
 171
 0172            var mouseIsDown = Input.GetMouseButtonDown(0);
 0173            var mouseIsUp = Input.GetMouseButtonUp(0);
 174
 0175            if (raycastHandlerTarget is IRaycastPointerDownHandler down)
 176            {
 0177                if (mouseIsDown)
 0178                    down.OnPointerDown();
 179            }
 180
 0181            if (raycastHandlerTarget is IRaycastPointerUpHandler up)
 182            {
 0183                if (mouseIsUp)
 0184                    up.OnPointerUp();
 185            }
 186
 0187            if (raycastHandlerTarget is IRaycastPointerClickHandler click)
 188            {
 0189                if (mouseIsDown)
 0190                    clickHandler = click;
 191
 0192                if (mouseIsUp)
 193                {
 0194                    if (clickHandler == click)
 0195                        click.OnPointerClick();
 0196                    clickHandler = null;
 197                }
 198            }
 0199        }
 200
 201        void UnhoverLastHoveredObject(InteractionHoverCanvasController interactionHoverCanvasController)
 202        {
 13720203            if (lastHoveredObject == null)
 204            {
 13715205                interactionHoverCanvasController.SetHoverState(false);
 13715206                return;
 207            }
 208
 5209            OnPointerHoverEnds?.Invoke();
 210
 20211            for (int i = 0; i < lastHoveredEventList.Length; i++)
 212            {
 5213                if (lastHoveredEventList[i] == null)
 214                    continue;
 5215                lastHoveredEventList[i].SetHoverState(false);
 216            }
 217
 5218            lastHoveredEventList = null;
 5219            lastHoveredObject = null;
 5220        }
 221
 222        public void Cleanup()
 223        {
 21630224            for (int i = 0; i < Enum.GetValues(typeof(WebInterface.ACTION_BUTTON)).Length; i++)
 225            {
 10094226                var buttonId = (WebInterface.ACTION_BUTTON)i;
 227
 10094228                if (buttonId == WebInterface.ACTION_BUTTON.ANY)
 229                    continue;
 230
 9373231                InputController_Legacy.i.RemoveListener(buttonId, OnButtonEvent);
 232            }
 233
 721234            lastHoveredObject = null;
 721235            newHoveredGO = null;
 721236            newHoveredEvent = null;
 721237            lastHoveredEventList = null;
 238
 721239            if (CursorController.i != null)
 240            {
 700241                OnPointerHoverStarts -= CursorController.i.SetHoverCursor;
 700242                OnPointerHoverEnds -= CursorController.i.SetNormalCursor;
 243            }
 721244        }
 245
 246        void RetrieveCamera()
 247        {
 695248            if (charCamera == null)
 249            {
 695250                charCamera = Camera.main;
 251            }
 695252        }
 253
 13760254        public Ray GetRayFromCamera() { return charCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height /
 255
 256        void OnButtonEvent(WebInterface.ACTION_BUTTON buttonId, InputController_Legacy.EVENT evt, bool useRaycast, bool 
 257        {
 258            //TODO(Brian): We should remove this when we get a proper initialization layer
 14259            if (!EnvironmentSettings.RUNNING_TESTS)
 260            {
 0261                if (Utils.LockedThisFrame())
 0262                    return;
 263
 0264                if (!Utils.isCursorLocked || !renderingEnabled)
 0265                    return;
 266            }
 267
 14268            if (charCamera == null)
 269            {
 0270                RetrieveCamera();
 271
 0272                if (charCamera == null)
 0273                    return;
 274            }
 275
 14276            var pointerEventLayer = PhysicsLayers.physicsCastLayerMaskWithoutCharacter; //Ensure characterController is 
 14277            var globalLayer = pointerEventLayer & ~PhysicsLayers.physicsCastLayerMask;
 278
 14279            if (evt == InputController_Legacy.EVENT.BUTTON_DOWN)
 280            {
 11281                ProcessButtonDown(buttonId, useRaycast, enablePointerEvent, pointerEventLayer, globalLayer);
 11282            }
 3283            else if (evt == InputController_Legacy.EVENT.BUTTON_UP)
 284            {
 3285                ProcessButtonUp(buttonId, useRaycast, enablePointerEvent, pointerEventLayer, globalLayer);
 286            }
 3287        }
 288
 289        private void ProcessButtonUp(WebInterface.ACTION_BUTTON buttonId, bool useRaycast, bool enablePointerEvent, Laye
 290        {
 3291            IWorldState worldState = Environment.i.world.state;
 292
 3293            if (string.IsNullOrEmpty(worldState.currentSceneId))
 0294                return;
 295
 296            RaycastHitInfo raycastGlobalLayerHitInfo;
 3297            Ray ray = GetRayFromCamera();
 298
 299            // Raycast for global pointer events
 3300            RaycastResultInfo raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer,
 3301            raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 302
 3303            if (pointerUpEvent != null)
 304            {
 305                // Raycast for pointer event components
 3306                RaycastResultInfo raycastInfoPointerEventLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, po
 307
 3308                bool isOnClickComponentBlocked = IsBlockingOnClick(raycastInfoPointerEventLayer.hitInfo, raycastGlobalLa
 3309                bool isSameEntityThatWasPressed = AreCollidersFromSameEntity(raycastInfoPointerEventLayer.hitInfo, lastP
 310
 3311                if (!isOnClickComponentBlocked && isSameEntityThatWasPressed && enablePointerEvent)
 312                {
 3313                    pointerUpEvent.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 314                }
 315
 3316                pointerUpEvent = null;
 317            }
 318
 3319            ReportGlobalPointerUpEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, worldSta
 320
 321            // Raycast for global pointer events (for each PE scene)
 3322            List<string> currentPortableExperienceIds = WorldStateUtils.GetActivePortableExperienceIds();
 6323            for (int i = 0; i < currentPortableExperienceIds.Count; i++)
 324            {
 0325                raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer, worldState.lo
 0326                raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 327
 0328                ReportGlobalPointerUpEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, curr
 329            }
 3330        }
 331
 332        private void ProcessButtonDown(WebInterface.ACTION_BUTTON buttonId, bool useRaycast, bool enablePointerEvent, La
 333        {
 11334            IWorldState worldState = Environment.i.world.state;
 335
 11336            if (string.IsNullOrEmpty(worldState.currentSceneId))
 0337                return;
 338
 339            RaycastHitInfo raycastGlobalLayerHitInfo;
 11340            Ray ray = GetRayFromCamera();
 341
 342            // Raycast for pointer event components
 11343            RaycastResultInfo raycastInfoPointerEventLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, pointe
 344
 345            // Raycast for global pointer events
 11346            RaycastResultInfo raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer,
 11347            raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 348
 11349            bool isOnClickComponentBlocked = IsBlockingOnClick(raycastInfoPointerEventLayer.hitInfo, raycastGlobalLayerH
 350
 11351            if (!isOnClickComponentBlocked && raycastInfoPointerEventLayer.hitInfo.hit.collider)
 352            {
 11353                Collider collider = raycastInfoPointerEventLayer.hitInfo.hit.collider;
 354
 355                GameObject hitGameObject;
 356
 11357                if (CollidersManager.i.GetColliderInfo(collider, out ColliderInfo info))
 11358                    hitGameObject = info.entity.gameObject;
 359                else
 0360                    hitGameObject = collider.gameObject;
 361
 11362                var events = hitGameObject.GetComponentsInChildren<IPointerEvent>();
 363
 40364                for (var i = 0; i < events.Length; i++)
 365                {
 9366                    IPointerEvent e = events[i];
 9367                    bool areSameEntity = AreSameEntity(e, info);
 368
 9369                    switch (e.GetEventType())
 370                    {
 371                        case PointerEventType.CLICK:
 1372                            if (areSameEntity && enablePointerEvent)
 1373                                e.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 1374                            break;
 375                        case PointerEventType.DOWN:
 5376                            if (areSameEntity && enablePointerEvent)
 4377                                e.Report(buttonId, ray, raycastInfoPointerEventLayer.hitInfo.hit);
 4378                            break;
 379                        case PointerEventType.UP:
 3380                            if (areSameEntity && enablePointerEvent)
 3381                                pointerUpEvent = e;
 382                            else
 0383                                pointerUpEvent = null;
 384                            break;
 385                    }
 386                }
 387
 11388                lastPointerDownEventHitInfo = raycastInfoPointerEventLayer.hitInfo;
 389            }
 390
 11391            ReportGlobalPointerDownEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, worldS
 392
 393            // Raycast for global pointer events (for each PE scene)
 11394            List<string> currentPortableExperienceIds = WorldStateUtils.GetActivePortableExperienceIds();
 22395            for (int i = 0; i < currentPortableExperienceIds.Count; i++)
 396            {
 0397                raycastInfoGlobalLayer = raycastHandler.Raycast(ray, charCamera.farClipPlane, globalLayer, worldState.lo
 0398                raycastGlobalLayerHitInfo = raycastInfoGlobalLayer.hitInfo;
 399
 0400                ReportGlobalPointerDownEvent(buttonId, useRaycast, raycastGlobalLayerHitInfo, raycastInfoGlobalLayer, cu
 401            }
 11402        }
 403
 404        private void ReportGlobalPointerUpEvent(
 405            WebInterface.ACTION_BUTTON buttonId,
 406            bool useRaycast,
 407            RaycastHitInfo raycastGlobalLayerHitInfo,
 408            RaycastResultInfo raycastInfoGlobalLayer,
 409            string sceneId)
 410        {
 3411            if (useRaycast && raycastGlobalLayerHitInfo.isValid)
 412            {
 3413                CollidersManager.i.GetColliderInfo(raycastGlobalLayerHitInfo.hit.collider, out ColliderInfo colliderInfo
 414
 3415                WebInterface.ReportGlobalPointerUpEvent(
 416                    buttonId,
 417                    raycastInfoGlobalLayer.ray,
 418                    raycastGlobalLayerHitInfo.hit.point,
 419                    raycastGlobalLayerHitInfo.hit.normal,
 420                    raycastGlobalLayerHitInfo.hit.distance,
 421                    sceneId,
 422                    colliderInfo.entity != null ? colliderInfo.entity.entityId : null,
 423                    colliderInfo.meshName,
 424                    isHitInfoValid: true);
 3425            }
 426            else
 427            {
 0428                WebInterface.ReportGlobalPointerUpEvent(buttonId, raycastInfoGlobalLayer.ray, Vector3.zero, Vector3.zero
 429            }
 0430        }
 431
 432        private void ReportGlobalPointerDownEvent(
 433            WebInterface.ACTION_BUTTON buttonId,
 434            bool useRaycast,
 435            RaycastHitInfo raycastGlobalLayerHitInfo,
 436            RaycastResultInfo raycastInfoGlobalLayer,
 437            string sceneId)
 438        {
 11439            if (useRaycast && raycastGlobalLayerHitInfo.isValid)
 440            {
 11441                CollidersManager.i.GetColliderInfo(raycastGlobalLayerHitInfo.hit.collider, out ColliderInfo colliderInfo
 442
 11443                WebInterface.ReportGlobalPointerDownEvent(
 444                    buttonId,
 445                    raycastInfoGlobalLayer.ray,
 446                    raycastGlobalLayerHitInfo.hit.point,
 447                    raycastGlobalLayerHitInfo.hit.normal,
 448                    raycastGlobalLayerHitInfo.hit.distance,
 449                    sceneId,
 450                    colliderInfo.entity != null ? colliderInfo.entity.entityId : null,
 451                    colliderInfo.meshName,
 452                    isHitInfoValid: true);
 11453            }
 454            else
 455            {
 0456                WebInterface.ReportGlobalPointerDownEvent(buttonId, raycastInfoGlobalLayer.ray, Vector3.zero, Vector3.ze
 457            }
 0458        }
 459
 50460        bool AreSameEntity(IPointerEvent pointerEvent, ColliderInfo colliderInfo) { return pointerEvent != null && colli
 461
 462        bool IsBlockingOnClick(RaycastHitInfo targetOnClickHit, RaycastHitInfo potentialBlockerHit)
 463        {
 14464            return
 465                potentialBlockerHit.hit.collider != null // Does a potential blocker hit exist?
 466                && targetOnClickHit.hit.collider != null // Was a target entity with a pointer event component hit?
 467                && potentialBlockerHit.hit.distance <= targetOnClickHit.hit.distance // Is potential blocker nearer than
 468                && !AreCollidersFromSameEntity(potentialBlockerHit, targetOnClickHit); // Does potential blocker belong 
 469        }
 470
 471        bool EntityHasPointerEvent(IDCLEntity entity)
 472        {
 34473            return entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_CALLBACK) ||
 474                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_UP) ||
 475                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_DOWN) ||
 476                   entity.components.ContainsKey(Models.CLASS_ID_COMPONENT.UUID_ON_CLICK);
 477        }
 478
 479        bool AreCollidersFromSameEntity(RaycastHitInfo hitInfoA, RaycastHitInfo hitInfoB)
 480        {
 17481            CollidersManager.i.GetColliderInfo(hitInfoA.hit.collider, out ColliderInfo colliderInfoA);
 17482            CollidersManager.i.GetColliderInfo(hitInfoB.hit.collider, out ColliderInfo colliderInfoB);
 483
 17484            var entityA = colliderInfoA.entity;
 17485            var entityB = colliderInfoB.entity;
 486
 17487            bool entityAHasEvent = entityA != null && EntityHasPointerEvent(entityA);
 17488            bool entityBHasEvent = entityB != null && EntityHasPointerEvent(entityB);
 489
 490            // If both entities has OnClick/PointerEvent component
 17491            if (entityAHasEvent && entityBHasEvent)
 492            {
 14493                return entityA == entityB;
 494            }
 495            // If only one of them has OnClick/PointerEvent component
 3496            else if (entityAHasEvent ^ entityBHasEvent)
 497            {
 0498                return false;
 499            }
 500            // None of them has OnClick/PointerEvent component
 501            else
 502            {
 3503                return colliderInfoA.entity == colliderInfoB.entity;
 504            }
 505        }
 506    }
 507}