< Summary

Class:DCL.Components.UIShape[ReferencesContainerType,ModelType]
Assembly:DCL.Components.UI
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIShape.cs
Covered lines:28
Uncovered lines:0
Coverable lines:28
Total lines:528
Line coverage:100% (28 of 28)
Covered branches:0
Total branches:0
Covered methods:8
Total methods:8
Method coverage:100% (8 of 8)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
UIShape()0%110100%
CreateUpdateHandler()0%110100%
PreApplyChanges(...)0%330100%
RaiseOnAppliedChanges()0%10100100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIShape.cs

#LineLine coverage
 1using DCL.Helpers;
 2using DCL.Models;
 3using System.Collections;
 4using System.Collections.Generic;
 5using DCL.Components.Interfaces;
 6using UnityEngine;
 7using UnityEngine.Assertions;
 8using UnityEngine.UI;
 9using Object = UnityEngine.Object;
 10using Decentraland.Sdk.Ecs6;
 11using MainScripts.DCL.Components;
 12
 13namespace DCL.Components
 14{
 15    public class UIShape<ReferencesContainerType, ModelType> : UIShape
 16        where ReferencesContainerType : UIReferencesContainer
 17        where ModelType : UIShape.Model
 18    {
 19        public const float RAYCAST_ALPHA_THRESHOLD = 0.01f;
 20
 16021        public UIShape() { }
 22
 247723        new public ModelType model { get { return base.model as ModelType; } set { base.model = value; } }
 24
 220725        new public ReferencesContainerType referencesContainer { get { return base.referencesContainer as ReferencesCont
 26
 8027        public override ComponentUpdateHandler CreateUpdateHandler() { return new UIShapeUpdateHandler<ReferencesContain
 28
 29        bool raiseOnAttached;
 30        bool firstApplyChangesCall;
 31
 32
 33        /// <summary>
 34        /// This is called by UIShapeUpdateHandler before calling ApplyChanges.
 35        /// </summary>
 36        public void PreApplyChanges(BaseModel newModel)
 37        {
 14938            model = (ModelType) newModel;
 39
 14940            raiseOnAttached = false;
 14941            firstApplyChangesCall = false;
 42
 14943            if (referencesContainer == null)
 44            {
 8045                referencesContainer = InstantiateUIGameObject<ReferencesContainerType>(referencesContainerPrefabName);
 46
 8047                raiseOnAttached = true;
 8048                firstApplyChangesCall = true;
 49            }
 6950            else if (ReparentComponent(referencesContainer.rectTransform, model.parentComponent))
 51            {
 4752                raiseOnAttached = true;
 53            }
 6954        }
 55
 56        public override void RaiseOnAppliedChanges()
 57        {
 14958            RefreshDCLLayout();
 59
 60#if UNITY_EDITOR
 14961            SetComponentDebugName();
 62#endif
 63
 64            // We hide the component visibility when it's created (first applychanges)
 65            // as it has default values and appears in the middle of the screen
 14966            if (firstApplyChangesCall)
 8067                referencesContainer.canvasGroup.alpha = 0f;
 68            else
 6969                referencesContainer.canvasGroup.alpha = model.visible ? model.opacity : 0f;
 70
 14971            referencesContainer.canvasGroup.blocksRaycasts = model.visible && model.isPointerBlocker;
 72
 14973            base.RaiseOnAppliedChanges();
 74
 14975            if (raiseOnAttached && parentUIComponent != null)
 76            {
 12777                UIReferencesContainer[] parents = referencesContainer.GetComponentsInParent<UIReferencesContainer>(true)
 78
 58679                for (int i = 0; i < parents.Length; i++)
 80                {
 16681                    UIReferencesContainer parent = parents[i];
 16682                    if (parent.owner != null)
 83                    {
 16384                        parent.owner.OnChildAttached(parentUIComponent, this);
 85                    }
 86                }
 87            }
 14988        }
 89    }
 90
 91    public class UIShape : BaseDisposable, IUIRefreshable
 92    {
 93        [System.Serializable]
 94        public class Model : BaseModel
 95        {
 96            public string name;
 97            public string parentComponent;
 98            public bool visible = true;
 99            public float opacity = 1f;
 100            public string hAlign = "center";
 101            public string vAlign = "center";
 102            public UIValue width = new (100f);
 103            public UIValue height = new (50f);
 104            public UIValue positionX = new (0f);
 105            public UIValue positionY = new (0f);
 106            public bool isPointerBlocker = true;
 107            public string onClick;
 108
 109            public override BaseModel GetDataFromJSON(string json) =>
 110                Utils.SafeFromJson<Model>(json);
 111
 112            public override BaseModel GetDataFromPb(ComponentBodyPayload pbModel)
 113            {
 114
 115                switch (pbModel.PayloadCase)
 116                {
 117                    case ComponentBodyPayload.PayloadOneofCase.UiShape:
 118                        var uiShapeModel = new Model();
 119                        if (pbModel.UiShape.HasName) uiShapeModel.name = pbModel.UiShape.Name;
 120                        if (pbModel.UiShape.HasParentComponent) uiShapeModel.parentComponent = pbModel.UiShape.ParentCom
 121                        if (pbModel.UiShape.HasVisible) uiShapeModel.visible = pbModel.UiShape.Visible;
 122                        if (pbModel.UiShape.HasOpacity) uiShapeModel.opacity = pbModel.UiShape.Opacity;
 123                        if (pbModel.UiShape.HasHAlign) uiShapeModel.hAlign = pbModel.UiShape.HAlign;
 124                        if (pbModel.UiShape.HasVAlign) uiShapeModel.vAlign = pbModel.UiShape.VAlign;
 125                        if (pbModel.UiShape.Width != null) uiShapeModel.width = SDK6DataMapExtensions.FromProtobuf(uiSha
 126                        if (pbModel.UiShape.Height != null) uiShapeModel.height = SDK6DataMapExtensions.FromProtobuf(uiS
 127                        if (pbModel.UiShape.PositionX != null) uiShapeModel.positionX = SDK6DataMapExtensions.FromProtob
 128                        if (pbModel.UiShape.PositionY != null) uiShapeModel.positionY = SDK6DataMapExtensions.FromProtob
 129                        if (pbModel.UiShape.HasIsPointerBlocker) uiShapeModel.isPointerBlocker = pbModel.UiShape.IsPoint
 130                        return uiShapeModel;
 131
 132                    case ComponentBodyPayload.PayloadOneofCase.UiScreenSpaceShape:
 133                        var screenSpaceModel = new Model();
 134                        if (pbModel.UiScreenSpaceShape.HasName) screenSpaceModel.name = pbModel.UiScreenSpaceShape.Name;
 135                        if (pbModel.UiScreenSpaceShape.HasParentComponent) screenSpaceModel.parentComponent = pbModel.Ui
 136                        if (pbModel.UiScreenSpaceShape.HasVisible) screenSpaceModel.visible = pbModel.UiScreenSpaceShape
 137                        if (pbModel.UiScreenSpaceShape.HasOpacity) screenSpaceModel.opacity = pbModel.UiScreenSpaceShape
 138                        if (pbModel.UiScreenSpaceShape.HasHAlign) screenSpaceModel.hAlign = pbModel.UiScreenSpaceShape.H
 139                        if (pbModel.UiScreenSpaceShape.HasVAlign) screenSpaceModel.vAlign = pbModel.UiScreenSpaceShape.V
 140                        if (pbModel.UiScreenSpaceShape.Width != null) screenSpaceModel.width = SDK6DataMapExtensions.Fro
 141                        if (pbModel.UiScreenSpaceShape.Height != null) screenSpaceModel.height = SDK6DataMapExtensions.F
 142                        if (pbModel.UiScreenSpaceShape.PositionX != null) screenSpaceModel.positionX = SDK6DataMapExtens
 143                        if (pbModel.UiScreenSpaceShape.PositionY != null) screenSpaceModel.positionY = SDK6DataMapExtens
 144                        if (pbModel.UiScreenSpaceShape.HasIsPointerBlocker) screenSpaceModel.isPointerBlocker = pbModel.
 145                        return screenSpaceModel;
 146
 147                    case ComponentBodyPayload.PayloadOneofCase.UiFullScreenShape:
 148                        var fullScreenModel = new Model();
 149                        if (pbModel.UiFullScreenShape.HasName) fullScreenModel.name = pbModel.UiFullScreenShape.Name;
 150                        if (pbModel.UiFullScreenShape.HasParentComponent) fullScreenModel.parentComponent = pbModel.UiFu
 151                        if (pbModel.UiFullScreenShape.HasVisible) fullScreenModel.visible = pbModel.UiFullScreenShape.Vi
 152                        if (pbModel.UiFullScreenShape.HasOpacity) fullScreenModel.opacity = pbModel.UiFullScreenShape.Op
 153                        if (pbModel.UiFullScreenShape.HasHAlign) fullScreenModel.hAlign = pbModel.UiFullScreenShape.HAli
 154                        if (pbModel.UiFullScreenShape.HasVAlign) fullScreenModel.vAlign = pbModel.UiFullScreenShape.VAli
 155                        if (pbModel.UiFullScreenShape.Width != null) fullScreenModel.width = SDK6DataMapExtensions.FromP
 156                        if (pbModel.UiFullScreenShape.Height != null) fullScreenModel.height = SDK6DataMapExtensions.Fro
 157                        if (pbModel.UiFullScreenShape.PositionX != null) fullScreenModel.positionX = SDK6DataMapExtensio
 158                        if (pbModel.UiFullScreenShape.PositionY != null) fullScreenModel.positionY = SDK6DataMapExtensio
 159                        if (pbModel.UiFullScreenShape.HasIsPointerBlocker) fullScreenModel.isPointerBlocker = pbModel.Ui
 160                        return fullScreenModel;
 161
 162                    default:
 163                        return Utils.SafeUnimplemented<UIShape, Model>(expected: ComponentBodyPayload.PayloadOneofCase.U
 164                }
 165            }
 166        }
 167
 168        public override string componentName => GetDebugName();
 169        public virtual string referencesContainerPrefabName => "";
 170        public UIReferencesContainer referencesContainer;
 171        public RectTransform childHookRectTransform;
 172
 173        public bool isLayoutDirty { get; private set; }
 174        protected System.Action OnLayoutRefresh;
 175
 176        private BaseVariable<Vector2Int> screenSize => DataStore.i.screen.size;
 177        private BaseVariable<Dictionary<int, Queue<IUIRefreshable>>> dirtyShapesBySceneVariable => DataStore.i.HUDs.dirt
 178        public UIShape parentUIComponent { get; protected set; }
 179
 180        public UIShape()
 181        {
 182            screenSize.OnChange += OnScreenResize;
 183            model = new Model();
 184        }
 185
 186        private void OnScreenResize(Vector2Int current, Vector2Int previous)
 187        {
 188            if (GetRootParent() == this)
 189                RequestRefresh();
 190        }
 191
 192        public override int GetClassId() { return (int) CLASS_ID.UI_IMAGE_SHAPE; }
 193
 194        public string GetDebugName()
 195        {
 196            Model model = (Model) this.model;
 197
 198            if (string.IsNullOrEmpty(model.name))
 199            {
 200                return GetType().Name;
 201            }
 202            else
 203            {
 204                return GetType().Name + " - " + model.name;
 205            }
 206        }
 207
 208        public override IEnumerator ApplyChanges(BaseModel newJson) { return null; }
 209
 210
 211        internal T InstantiateUIGameObject<T>(string prefabPath) where T : UIReferencesContainer
 212        {
 213            Model model = (Model) this.model;
 214
 215            GameObject uiGameObject = null;
 216
 217            bool targetParentExists = !string.IsNullOrEmpty(model.parentComponent) &&
 218                                      scene.componentsManagerLegacy.HasSceneSharedComponent(model.parentComponent);
 219
 220            if (targetParentExists)
 221            {
 222                if (scene.componentsManagerLegacy.HasSceneSharedComponent(model.parentComponent))
 223                {
 224                    parentUIComponent = (scene.componentsManagerLegacy.GetSceneSharedComponent(model.parentComponent) as
 225                }
 226                else
 227                {
 228                    parentUIComponent = scene.componentsManagerLegacy.GetSceneSharedComponent<UIScreenSpace>();
 229                }
 230            }
 231            else
 232            {
 233                parentUIComponent = scene.componentsManagerLegacy.GetSceneSharedComponent<UIScreenSpace>();
 234            }
 235
 236            uiGameObject =
 237                Object.Instantiate(
 238                    Resources.Load(prefabPath),
 239                    parentUIComponent?.childHookRectTransform) as GameObject;
 240
 241            referencesContainer = uiGameObject.GetComponent<T>();
 242
 243            referencesContainer.rectTransform.SetToMaxStretch();
 244
 245            childHookRectTransform = referencesContainer.childHookRectTransform;
 246
 247            referencesContainer.owner = this;
 248
 249            return referencesContainer as T;
 250        }
 251
 252        public virtual void RequestRefresh()
 253        {
 254            if (isLayoutDirty) return;
 255
 256            isLayoutDirty = true;
 257
 258            var dirtyShapesByScene = dirtyShapesBySceneVariable.Get();
 259
 260            int sceneDataSceneNumber = scene.sceneData.sceneNumber;
 261            if (sceneDataSceneNumber <= 0) sceneDataSceneNumber = 666;
 262
 263            if (!dirtyShapesByScene.ContainsKey(sceneDataSceneNumber))
 264            {
 265                dirtyShapesByScene.Add(sceneDataSceneNumber, new Queue<IUIRefreshable>());
 266            }
 267
 268            dirtyShapesByScene[sceneDataSceneNumber].Enqueue(this);
 269        }
 270
 271        private void RefreshRecursively()
 272        {
 273            // We are not using the _Internal here because the method is overridden
 274            // by some UI shapes.
 275            RefreshDCLLayoutRecursively(refreshSize: true, refreshAlignmentAndPosition: false);
 276            FixMaxStretchRecursively();
 277            RefreshDCLLayoutRecursively_Internal(refreshSize: false, refreshAlignmentAndPosition: true);
 278        }
 279
 280        public virtual void MarkLayoutDirty( System.Action OnRefresh = null )
 281        {
 282            UIShape rootParent = GetRootParent();
 283
 284            Assert.IsTrue(rootParent != null, "root parent must never be null");
 285
 286            if (rootParent.referencesContainer == null)
 287                return;
 288
 289            rootParent.RequestRefresh();
 290
 291            if ( OnRefresh != null )
 292                rootParent.OnLayoutRefresh += OnRefresh;
 293        }
 294
 295        public void RefreshDCLLayout(bool refreshSize = true, bool refreshAlignmentAndPosition = true)
 296        {
 297            RectTransform parentRT = referencesContainer.GetComponentInParent<RectTransform>();
 298
 299            if (refreshSize)
 300            {
 301                RefreshDCLSize(parentRT);
 302            }
 303
 304            if (refreshAlignmentAndPosition)
 305            {
 306                // Alignment (Alignment uses size so we should always align AFTER resizing)
 307                RefreshDCLAlignmentAndPosition(parentRT);
 308            }
 309        }
 310
 311        protected virtual void RefreshDCLSize(RectTransform parentTransform = null)
 312        {
 313            if (parentTransform == null)
 314                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 315
 316            Model model = (Model) this.model;
 317
 318            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal,
 319                model.width.GetScaledValue(parentTransform.rect.width));
 320            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical,
 321                model.height.GetScaledValue(parentTransform.rect.height));
 322        }
 323
 324        public void RefreshDCLAlignmentAndPosition(RectTransform parentTransform = null)
 325        {
 326            if (parentTransform == null)
 327                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 328
 329            referencesContainer.layoutElement.ignoreLayout = false;
 330            ConfigureAlignment(referencesContainer.layoutGroup);
 331            Utils.ForceRebuildLayoutImmediate(parentTransform);
 332            referencesContainer.layoutElement.ignoreLayout = true;
 333
 334            Model model = (Model) this.model;
 335
 336            // Reposition
 337            Vector3 position = Vector3.zero;
 338            position.x = model.positionX.GetScaledValue(parentTransform.rect.width);
 339            position.y = model.positionY.GetScaledValue(parentTransform.rect.height);
 340
 341            position = Utils.Sanitize(position);
 342            referencesContainer.layoutElementRT.localPosition += position;
 343        }
 344
 345        public virtual void RefreshDCLLayoutRecursively(bool refreshSize = true,
 346            bool refreshAlignmentAndPosition = true)
 347        {
 348            RefreshDCLLayoutRecursively_Internal(refreshSize, refreshAlignmentAndPosition);
 349        }
 350
 351        public void RefreshDCLLayoutRecursively_Internal(bool refreshSize = true,
 352            bool refreshAlignmentAndPosition = true)
 353        {
 354            UIShape rootParent = GetRootParent();
 355
 356            Assert.IsTrue(rootParent != null, "root parent must never be null");
 357
 358            if (rootParent.referencesContainer == null)
 359                return;
 360
 361            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 362                (x) =>
 363                {
 364                    if (x.owner != null)
 365                        x.owner.RefreshDCLLayout(refreshSize, refreshAlignmentAndPosition);
 366                },
 367                rootParent.referencesContainer.transform);
 368        }
 369
 370        public void FixMaxStretchRecursively()
 371        {
 372            UIShape rootParent = GetRootParent();
 373
 374            Assert.IsTrue(rootParent != null, "root parent must never be null");
 375
 376            if (rootParent.referencesContainer == null)
 377                return;
 378
 379            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 380                (x) =>
 381                {
 382                    if (x.owner != null)
 383                    {
 384                        x.rectTransform.SetToMaxStretch();
 385                    }
 386                },
 387                rootParent.referencesContainer.transform);
 388        }
 389
 390        protected bool ReparentComponent(RectTransform targetTransform, string targetParent)
 391        {
 392            bool targetParentExists = !string.IsNullOrEmpty(targetParent) &&
 393                                      scene.componentsManagerLegacy.HasSceneSharedComponent(targetParent);
 394
 395            if (targetParentExists && parentUIComponent == scene.componentsManagerLegacy.GetSceneSharedComponent(targetP
 396            {
 397                return false;
 398            }
 399
 400            if (parentUIComponent != null)
 401            {
 402                UIReferencesContainer[] parents = referencesContainer.GetComponentsInParent<UIReferencesContainer>(true)
 403
 404                foreach (var parent in parents)
 405                {
 406                    if (parent.owner != null)
 407                    {
 408                        parent.owner.OnChildDetached(parentUIComponent, this);
 409                    }
 410                }
 411            }
 412
 413            if (targetParentExists)
 414            {
 415                parentUIComponent = scene.componentsManagerLegacy.GetSceneSharedComponent(targetParent) as UIShape;
 416            }
 417            else
 418            {
 419                parentUIComponent = scene.componentsManagerLegacy.GetSceneSharedComponent<UIScreenSpace>();
 420            }
 421
 422            targetTransform.SetParent(parentUIComponent.childHookRectTransform, false);
 423            return true;
 424        }
 425
 426        public UIShape GetRootParent()
 427        {
 428            UIShape parent = null;
 429
 430            if (parentUIComponent != null && !(parentUIComponent is UIScreenSpace))
 431            {
 432                parent = parentUIComponent.GetRootParent();
 433            }
 434            else
 435            {
 436                parent = this;
 437            }
 438
 439            return parent;
 440        }
 441
 442        protected void ConfigureAlignment(LayoutGroup layout)
 443        {
 444            Model model = (Model) this.model;
 445            switch (model.vAlign)
 446            {
 447                case "top":
 448                    switch (model.hAlign)
 449                    {
 450                        case "left":
 451                            layout.childAlignment = TextAnchor.UpperLeft;
 452                            break;
 453                        case "right":
 454                            layout.childAlignment = TextAnchor.UpperRight;
 455                            break;
 456                        default:
 457                            layout.childAlignment = TextAnchor.UpperCenter;
 458                            break;
 459                    }
 460
 461                    break;
 462                case "bottom":
 463                    switch (model.hAlign)
 464                    {
 465                        case "left":
 466                            layout.childAlignment = TextAnchor.LowerLeft;
 467                            break;
 468                        case "right":
 469                            layout.childAlignment = TextAnchor.LowerRight;
 470                            break;
 471                        default:
 472                            layout.childAlignment = TextAnchor.LowerCenter;
 473                            break;
 474                    }
 475
 476                    break;
 477                default: // center
 478                    switch (model.hAlign)
 479                    {
 480                        case "left":
 481                            layout.childAlignment = TextAnchor.MiddleLeft;
 482                            break;
 483                        case "right":
 484                            layout.childAlignment = TextAnchor.MiddleRight;
 485                            break;
 486                        default:
 487                            layout.childAlignment = TextAnchor.MiddleCenter;
 488                            break;
 489                    }
 490
 491                    break;
 492            }
 493        }
 494
 495        protected void SetComponentDebugName()
 496        {
 497            if (referencesContainer == null || model == null)
 498            {
 499                return;
 500            }
 501
 502            referencesContainer.name = componentName;
 503        }
 504
 505        public override void Dispose()
 506        {
 507
 508            if (childHookRectTransform)
 509                Utils.SafeDestroy(childHookRectTransform.gameObject);
 510
 511            screenSize.OnChange -= OnScreenResize;
 512
 513            base.Dispose();
 514        }
 515
 516        public virtual void OnChildAttached(UIShape parentComponent, UIShape childComponent) { }
 517
 518        public virtual void OnChildDetached(UIShape parentComponent, UIShape childComponent) { }
 519        public void Refresh()
 520        {
 521            RefreshRecursively();
 522            isLayoutDirty = false;
 523
 524            OnLayoutRefresh?.Invoke();
 525            OnLayoutRefresh = null;
 526        }
 527    }
 528}