< Summary

Class:DCL.Components.UIShape
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIShape.cs
Covered lines:152
Uncovered lines:12
Coverable lines:164
Total lines:502
Line coverage:92.6% (152 of 164)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Model()0%110100%
GetDataFromJSON(...)0%110100%
UIShape()0%220100%
OnScreenResize(...)0%110100%
GetClassId()0%2100%
GetDebugName()0%220100%
ApplyChanges(...)0%2100%
InstantiateUIGameObject[T](...)0%6.076087.5%
LayoutRefreshWatcher()0%550100%
RefreshAll()0%220100%
MarkLayoutDirty(...)0%3.023087.5%
RefreshDCLLayout(...)0%330100%
RefreshDCLSize(...)0%2.022083.33%
RefreshDCLAlignmentAndPosition(...)0%22092.31%
RefreshDCLLayoutRecursively(...)0%110100%
RefreshDCLLayoutRecursively_Internal(...)0%220100%
FixMaxStretchRecursively()0%330100%
ReparentComponent(...)0%880100%
GetRootParent()0%330100%
ConfigureAlignment(...)0%13130100%
SetComponentDebugName()0%3.143075%
Dispose()0%330100%
OnChildAttached(...)0%110100%
OnChildDetached(...)0%110100%

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 UnityEngine;
 5using UnityEngine.Assertions;
 6using UnityEngine.UI;
 7
 8namespace DCL.Components
 9{
 10    [System.Serializable]
 11    public struct UIValue
 12    {
 13        public enum Unit
 14        {
 15            PERCENT,
 16            PIXELS
 17        }
 18
 19        public float value;
 20        public Unit type;
 21
 22        public void SetPixels(float value)
 23        {
 24            this.type = Unit.PIXELS;
 25            this.value = value;
 26        }
 27
 28        public void SetPercent(float value)
 29        {
 30            this.type = Unit.PERCENT;
 31            this.value = value;
 32        }
 33
 34        public UIValue(float value, Unit unitType = Unit.PIXELS)
 35        {
 36            this.value = value;
 37            this.type = unitType;
 38        }
 39
 40        public float GetScaledValue(float parentSize)
 41        {
 42            if (type == Unit.PIXELS)
 43                return value;
 44
 45            // Prevent division by zero
 46            if (parentSize <= Mathf.Epsilon)
 47                parentSize = 1;
 48
 49            return value / 100 * parentSize;
 50        }
 51    }
 52
 53    public class UIShape<ReferencesContainerType, ModelType> : UIShape
 54        where ReferencesContainerType : UIReferencesContainer
 55        where ModelType : UIShape.Model
 56    {
 57        public UIShape() { }
 58
 59        new public ModelType model { get { return base.model as ModelType; } set { base.model = value; } }
 60
 61        new public ReferencesContainerType referencesContainer { get { return base.referencesContainer as ReferencesCont
 62
 63        public override ComponentUpdateHandler CreateUpdateHandler() { return new UIShapeUpdateHandler<ReferencesContain
 64
 65        bool raiseOnAttached;
 66        bool firstApplyChangesCall;
 67
 68        /// <summary>
 69        /// This is called by UIShapeUpdateHandler before calling ApplyChanges.
 70        /// </summary>
 71        public void PreApplyChanges(BaseModel newModel)
 72        {
 73            model = (ModelType) newModel;
 74
 75            raiseOnAttached = false;
 76            firstApplyChangesCall = false;
 77
 78            if (referencesContainer == null)
 79            {
 80                referencesContainer = InstantiateUIGameObject<ReferencesContainerType>(referencesContainerPrefabName);
 81
 82                raiseOnAttached = true;
 83                firstApplyChangesCall = true;
 84            }
 85            else if (ReparentComponent(referencesContainer.rectTransform, model.parentComponent))
 86            {
 87                raiseOnAttached = true;
 88            }
 89        }
 90
 91        public override void RaiseOnAppliedChanges()
 92        {
 93            RefreshDCLLayout();
 94
 95#if UNITY_EDITOR
 96            SetComponentDebugName();
 97#endif
 98
 99            // We hide the component visibility when it's created (first applychanges)
 100            // as it has default values and appears in the middle of the screen
 101            if (firstApplyChangesCall)
 102                referencesContainer.canvasGroup.alpha = 0f;
 103            else
 104                referencesContainer.canvasGroup.alpha = model.visible ? model.opacity : 0f;
 105
 106            referencesContainer.canvasGroup.blocksRaycasts = model.visible && model.isPointerBlocker;
 107
 108            base.RaiseOnAppliedChanges();
 109
 110            if (raiseOnAttached && parentUIComponent != null)
 111            {
 112                UIReferencesContainer[] parents = referencesContainer.GetComponentsInParent<UIReferencesContainer>(true)
 113
 114                for (int i = 0; i < parents.Length; i++)
 115                {
 116                    UIReferencesContainer parent = parents[i];
 117                    if (parent.owner != null)
 118                    {
 119                        parent.owner.OnChildAttached(parentUIComponent, this);
 120                    }
 121                }
 122            }
 123        }
 124    }
 125
 126    public class UIShape : BaseDisposable
 127    {
 128        [System.Serializable]
 129        public class Model : BaseModel
 130        {
 131            public string name;
 132            public string parentComponent;
 610133            public bool visible = true;
 610134            public float opacity = 1f;
 610135            public string hAlign = "center";
 610136            public string vAlign = "center";
 610137            public UIValue width = new UIValue(100f);
 610138            public UIValue height = new UIValue(50f);
 610139            public UIValue positionX = new UIValue(0f);
 610140            public UIValue positionY = new UIValue(0f);
 610141            public bool isPointerBlocker = true;
 142            public string onClick;
 143
 51144            public override BaseModel GetDataFromJSON(string json) { return Utils.SafeFromJson<Model>(json); }
 145        }
 146
 145147        public override string componentName => GetDebugName();
 0148        public virtual string referencesContainerPrefabName => "";
 149        public UIReferencesContainer referencesContainer;
 150        public RectTransform childHookRectTransform;
 151
 0152        public bool isLayoutDirty { get; protected set; }
 153        protected System.Action OnLayoutRefresh;
 154
 0155        private BaseVariable<Vector2Int> screenSize => DataStore.i.screen.size;
 156
 0157        public UIShape parentUIComponent { get; protected set; }
 158
 159        private Coroutine layoutRefreshWatcher;
 160
 120161        public UIShape()
 162        {
 120163            screenSize.OnChange += OnScreenResize;
 120164            model = new Model();
 165
 120166            if ( layoutRefreshWatcher == null )
 120167                layoutRefreshWatcher = CoroutineStarter.Start(LayoutRefreshWatcher());
 120168        }
 169
 37170        private void OnScreenResize(Vector2Int current, Vector2Int previous) => RefreshAll();
 171
 0172        public override int GetClassId() { return (int) CLASS_ID.UI_IMAGE_SHAPE; }
 173
 174        public string GetDebugName()
 175        {
 145176            Model model = (Model) this.model;
 177
 145178            if (string.IsNullOrEmpty(model.name))
 179            {
 138180                return GetType().Name;
 181            }
 182            else
 183            {
 7184                return GetType().Name + " - " + model.name;
 185            }
 186        }
 187
 188        public override IEnumerator ApplyChanges(BaseModel newJson)
 189        {
 0190            return null;
 191        }
 192
 193        internal T InstantiateUIGameObject<T>(string prefabPath) where T : UIReferencesContainer
 194        {
 77195            Model model = (Model) this.model;
 196
 77197            GameObject uiGameObject = null;
 198
 77199            bool targetParentExists = !string.IsNullOrEmpty(model.parentComponent) &&
 200                                      scene.disposableComponents.ContainsKey(model.parentComponent);
 201
 77202            if (targetParentExists)
 203            {
 23204                if (scene.disposableComponents.ContainsKey(model.parentComponent))
 205                {
 23206                    parentUIComponent = (scene.disposableComponents[model.parentComponent] as UIShape);
 23207                }
 208                else
 209                {
 0210                    parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 211                }
 0212            }
 213            else
 214            {
 54215                parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 216            }
 217
 77218            uiGameObject =
 219                Object.Instantiate(
 220                    Resources.Load(prefabPath),
 221                    parentUIComponent != null ? parentUIComponent.childHookRectTransform : null) as GameObject;
 222
 77223            referencesContainer = uiGameObject.GetComponent<T>();
 224
 77225            referencesContainer.rectTransform.SetToMaxStretch();
 226
 77227            childHookRectTransform = referencesContainer.childHookRectTransform;
 228
 77229            referencesContainer.owner = this;
 230
 77231            return referencesContainer as T;
 232        }
 233
 234        IEnumerator LayoutRefreshWatcher()
 235        {
 53236            while (true)
 237            {
 238                // WaitForEndOfFrame doesn't work in batch mode
 4392239                yield return Application.isBatchMode ? null : new WaitForEndOfFrame();
 240
 4272241                if ( !isLayoutDirty )
 242                    continue;
 243
 53244                RefreshAll();
 245            }
 246        }
 247
 248        public virtual void RefreshAll()
 249        {
 250            // We are not using the _Internal here because the method is overridden
 251            // by some UI shapes.
 90252            RefreshDCLLayoutRecursively(refreshSize: true, refreshAlignmentAndPosition: false);
 90253            FixMaxStretchRecursively();
 90254            RefreshDCLLayoutRecursively_Internal(refreshSize: false, refreshAlignmentAndPosition: true);
 90255            isLayoutDirty = false;
 90256            OnLayoutRefresh?.Invoke();
 90257            OnLayoutRefresh = null;
 90258        }
 259
 260        public virtual void MarkLayoutDirty( System.Action OnRefresh = null )
 261        {
 93262            UIShape rootParent = GetRootParent();
 263
 93264            Assert.IsTrue(rootParent != null, "root parent must never be null");
 265
 93266            if (rootParent.referencesContainer == null)
 0267                return;
 268
 93269            rootParent.isLayoutDirty = true;
 270
 93271            if ( OnRefresh != null )
 24272                rootParent.OnLayoutRefresh += OnRefresh;
 93273        }
 274
 275        public void RefreshDCLLayout(bool refreshSize = true, bool refreshAlignmentAndPosition = true)
 276        {
 365277            RectTransform parentRT = referencesContainer.GetComponentInParent<RectTransform>();
 278
 365279            if (refreshSize)
 280            {
 243281                RefreshDCLSize(parentRT);
 282            }
 283
 365284            if (refreshAlignmentAndPosition)
 285            {
 286                // Alignment (Alignment uses size so we should always align AFTER resizing)
 267287                RefreshDCLAlignmentAndPosition(parentRT);
 288            }
 365289        }
 290
 291        protected virtual void RefreshDCLSize(RectTransform parentTransform = null)
 292        {
 227293            if (parentTransform == null)
 0294                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 295
 227296            Model model = (Model) this.model;
 297
 227298            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal,
 299                model.width.GetScaledValue(parentTransform.rect.width));
 227300            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical,
 301                model.height.GetScaledValue(parentTransform.rect.height));
 227302        }
 303
 304        public void RefreshDCLAlignmentAndPosition(RectTransform parentTransform = null)
 305        {
 267306            if (parentTransform == null)
 0307                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 308
 267309            referencesContainer.layoutElement.ignoreLayout = false;
 267310            ConfigureAlignment(referencesContainer.layoutGroup);
 267311            Utils.ForceRebuildLayoutImmediate(parentTransform);
 267312            referencesContainer.layoutElement.ignoreLayout = true;
 313
 267314            Model model = (Model) this.model;
 315
 316            // Reposition
 267317            Vector3 position = Vector3.zero;
 267318            position.x = model.positionX.GetScaledValue(parentTransform.rect.width);
 267319            position.y = model.positionY.GetScaledValue(parentTransform.rect.height);
 320
 267321            position = Utils.Sanitize(position);
 267322            referencesContainer.layoutElementRT.localPosition += position;
 267323        }
 324
 325        public virtual void RefreshDCLLayoutRecursively(bool refreshSize = true,
 326            bool refreshAlignmentAndPosition = true)
 327        {
 114328            RefreshDCLLayoutRecursively_Internal(refreshSize, refreshAlignmentAndPosition);
 114329        }
 330
 331        public void RefreshDCLLayoutRecursively_Internal(bool refreshSize = true,
 332            bool refreshAlignmentAndPosition = true)
 333        {
 204334            UIShape rootParent = GetRootParent();
 335
 204336            Assert.IsTrue(rootParent != null, "root parent must never be null");
 337
 204338            if (rootParent.referencesContainer == null)
 68339                return;
 340
 136341            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 342                (x) =>
 343                {
 304344                    if (x.owner != null)
 220345                        x.owner.RefreshDCLLayout(refreshSize, refreshAlignmentAndPosition);
 304346                },
 347                rootParent.referencesContainer.transform);
 136348        }
 349
 350        public void FixMaxStretchRecursively()
 351        {
 90352            UIShape rootParent = GetRootParent();
 353
 90354            Assert.IsTrue(rootParent != null, "root parent must never be null");
 355
 90356            if (rootParent.referencesContainer == null)
 34357                return;
 358
 56359            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 360                (x) =>
 361                {
 140362                    if (x.owner != null)
 363                    {
 98364                        x.rectTransform.SetToMaxStretch();
 365                    }
 140366                },
 367                rootParent.referencesContainer.transform);
 56368        }
 369
 370        protected bool ReparentComponent(RectTransform targetTransform, string targetParent)
 371        {
 68372            bool targetParentExists = !string.IsNullOrEmpty(targetParent) &&
 373                                      scene.disposableComponents.ContainsKey(targetParent);
 374
 68375            if (targetParentExists && parentUIComponent == scene.disposableComponents[targetParent])
 376            {
 22377                return false;
 378            }
 379
 46380            if (parentUIComponent != null)
 381            {
 46382                UIReferencesContainer[] parents = referencesContainer.GetComponentsInParent<UIReferencesContainer>(true)
 383
 186384                foreach (var parent in parents)
 385                {
 47386                    if (parent.owner != null)
 387                    {
 47388                        parent.owner.OnChildDetached(parentUIComponent, this);
 389                    }
 390                }
 391            }
 392
 46393            if (targetParentExists)
 394            {
 22395                parentUIComponent = scene.disposableComponents[targetParent] as UIShape;
 22396            }
 397            else
 398            {
 24399                parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 400            }
 401
 46402            targetTransform.SetParent(parentUIComponent.childHookRectTransform, false);
 46403            return true;
 404        }
 405
 406        public UIShape GetRootParent()
 407        {
 393408            UIShape parent = null;
 409
 393410            if (parentUIComponent != null && !(parentUIComponent is UIScreenSpace))
 411            {
 6412                parent = parentUIComponent.GetRootParent();
 6413            }
 414            else
 415            {
 387416                parent = this;
 417            }
 418
 393419            return parent;
 420        }
 421
 422        protected void ConfigureAlignment(LayoutGroup layout)
 423        {
 267424            Model model = (Model) this.model;
 267425            switch (model.vAlign)
 426            {
 427                case "top":
 7428                    switch (model.hAlign)
 429                    {
 430                        case "left":
 2431                            layout.childAlignment = TextAnchor.UpperLeft;
 2432                            break;
 433                        case "right":
 2434                            layout.childAlignment = TextAnchor.UpperRight;
 2435                            break;
 436                        default:
 3437                            layout.childAlignment = TextAnchor.UpperCenter;
 3438                            break;
 439                    }
 440
 441                    break;
 442                case "bottom":
 26443                    switch (model.hAlign)
 444                    {
 445                        case "left":
 8446                            layout.childAlignment = TextAnchor.LowerLeft;
 8447                            break;
 448                        case "right":
 16449                            layout.childAlignment = TextAnchor.LowerRight;
 16450                            break;
 451                        default:
 2452                            layout.childAlignment = TextAnchor.LowerCenter;
 2453                            break;
 454                    }
 455
 456                    break;
 457                default: // center
 234458                    switch (model.hAlign)
 459                    {
 460                        case "left":
 2461                            layout.childAlignment = TextAnchor.MiddleLeft;
 2462                            break;
 463                        case "right":
 2464                            layout.childAlignment = TextAnchor.MiddleRight;
 2465                            break;
 466                        default:
 230467                            layout.childAlignment = TextAnchor.MiddleCenter;
 468                            break;
 469                    }
 470
 471                    break;
 472            }
 230473        }
 474
 475        protected void SetComponentDebugName()
 476        {
 145477            if (referencesContainer == null || model == null)
 478            {
 0479                return;
 480            }
 481
 145482            referencesContainer.name = componentName;
 145483        }
 484
 485        public override void Dispose()
 486        {
 84487            if (childHookRectTransform)
 51488                Utils.SafeDestroy(childHookRectTransform.gameObject);
 489
 84490            screenSize.OnChange -= OnScreenResize;
 491
 84492            if ( layoutRefreshWatcher != null )
 84493                CoroutineStarter.Stop(layoutRefreshWatcher);
 494
 84495            base.Dispose();
 84496        }
 497
 114498        public virtual void OnChildAttached(UIShape parentComponent, UIShape childComponent) { }
 499
 38500        public virtual void OnChildDetached(UIShape parentComponent, UIShape childComponent) { }
 501    }
 502}