< 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:156
Uncovered lines:11
Coverable lines:167
Total lines:511
Line coverage:93.4% (156 of 167)
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%770100%
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 const float RAYCAST_ALPHA_THRESHOLD = 0.01f;
 58
 59        public UIShape() { }
 60
 61        new public ModelType model { get { return base.model as ModelType; } set { base.model = value; } }
 62
 63        new public ReferencesContainerType referencesContainer { get { return base.referencesContainer as ReferencesCont
 64
 65        public override ComponentUpdateHandler CreateUpdateHandler() { return new UIShapeUpdateHandler<ReferencesContain
 66
 67        bool raiseOnAttached;
 68        bool firstApplyChangesCall;
 69
 70        /// <summary>
 71        /// This is called by UIShapeUpdateHandler before calling ApplyChanges.
 72        /// </summary>
 73        public void PreApplyChanges(BaseModel newModel)
 74        {
 75            model = (ModelType) newModel;
 76
 77            raiseOnAttached = false;
 78            firstApplyChangesCall = false;
 79
 80            if (referencesContainer == null)
 81            {
 82                referencesContainer = InstantiateUIGameObject<ReferencesContainerType>(referencesContainerPrefabName);
 83
 84                raiseOnAttached = true;
 85                firstApplyChangesCall = true;
 86            }
 87            else if (ReparentComponent(referencesContainer.rectTransform, model.parentComponent))
 88            {
 89                raiseOnAttached = true;
 90            }
 91        }
 92
 93        public override void RaiseOnAppliedChanges()
 94        {
 95            RefreshDCLLayout();
 96
 97#if UNITY_EDITOR
 98            SetComponentDebugName();
 99#endif
 100
 101            // We hide the component visibility when it's created (first applychanges)
 102            // as it has default values and appears in the middle of the screen
 103            if (firstApplyChangesCall)
 104                referencesContainer.canvasGroup.alpha = 0f;
 105            else
 106                referencesContainer.canvasGroup.alpha = model.visible ? model.opacity : 0f;
 107
 108            referencesContainer.canvasGroup.blocksRaycasts = model.visible && model.isPointerBlocker;
 109
 110            base.RaiseOnAppliedChanges();
 111
 112            if (raiseOnAttached && parentUIComponent != null)
 113            {
 114                UIReferencesContainer[] parents = referencesContainer.GetComponentsInParent<UIReferencesContainer>(true)
 115
 116                for (int i = 0; i < parents.Length; i++)
 117                {
 118                    UIReferencesContainer parent = parents[i];
 119                    if (parent.owner != null)
 120                    {
 121                        parent.owner.OnChildAttached(parentUIComponent, this);
 122                    }
 123                }
 124            }
 125        }
 126    }
 127
 128    public class UIShape : BaseDisposable
 129    {
 130        [System.Serializable]
 131        public class Model : BaseModel
 132        {
 133            public string name;
 134            public string parentComponent;
 618135            public bool visible = true;
 618136            public float opacity = 1f;
 618137            public string hAlign = "center";
 618138            public string vAlign = "center";
 618139            public UIValue width = new UIValue(100f);
 618140            public UIValue height = new UIValue(50f);
 618141            public UIValue positionX = new UIValue(0f);
 618142            public UIValue positionY = new UIValue(0f);
 618143            public bool isPointerBlocker = true;
 144            public string onClick;
 145
 52146            public override BaseModel GetDataFromJSON(string json) { return Utils.SafeFromJson<Model>(json); }
 147        }
 148
 146149        public override string componentName => GetDebugName();
 0150        public virtual string referencesContainerPrefabName => "";
 151        public UIReferencesContainer referencesContainer;
 152        public RectTransform childHookRectTransform;
 153
 0154        public bool isLayoutDirty { get; protected set; }
 155        protected System.Action OnLayoutRefresh;
 156
 129157        private BaseVariable<Vector2Int> screenSize => DataStore.i.screen.size;
 158
 0159        public UIShape parentUIComponent { get; protected set; }
 160
 161        private Coroutine layoutRefreshWatcher;
 162
 122163        public UIShape()
 164        {
 122165            screenSize.OnChange += OnScreenResize;
 122166            model = new Model();
 167
 122168            if ( layoutRefreshWatcher == null )
 122169                layoutRefreshWatcher = CoroutineStarter.Start(LayoutRefreshWatcher());
 122170        }
 171
 172        private void OnScreenResize(Vector2Int current, Vector2Int previous)
 173        {
 2174            isLayoutDirty = GetRootParent() == this;
 2175        }
 176
 0177        public override int GetClassId() { return (int) CLASS_ID.UI_IMAGE_SHAPE; }
 178
 179        public string GetDebugName()
 180        {
 146181            Model model = (Model) this.model;
 182
 146183            if (string.IsNullOrEmpty(model.name))
 184            {
 139185                return GetType().Name;
 186            }
 187            else
 188            {
 7189                return GetType().Name + " - " + model.name;
 190            }
 191        }
 192
 0193        public override IEnumerator ApplyChanges(BaseModel newJson) { return null; }
 194
 195        internal T InstantiateUIGameObject<T>(string prefabPath) where T : UIReferencesContainer
 196        {
 78197            Model model = (Model) this.model;
 198
 78199            GameObject uiGameObject = null;
 200
 78201            bool targetParentExists = !string.IsNullOrEmpty(model.parentComponent) &&
 202                                      scene.disposableComponents.ContainsKey(model.parentComponent);
 203
 78204            if (targetParentExists)
 205            {
 23206                if (scene.disposableComponents.ContainsKey(model.parentComponent))
 207                {
 23208                    parentUIComponent = (scene.disposableComponents[model.parentComponent] as UIShape);
 23209                }
 210                else
 211                {
 0212                    parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 213                }
 0214            }
 215            else
 216            {
 55217                parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 218            }
 219
 78220            uiGameObject =
 221                Object.Instantiate(
 222                    Resources.Load(prefabPath),
 223                    parentUIComponent != null ? parentUIComponent.childHookRectTransform : null) as GameObject;
 224
 78225            referencesContainer = uiGameObject.GetComponent<T>();
 226
 78227            referencesContainer.rectTransform.SetToMaxStretch();
 228
 78229            childHookRectTransform = referencesContainer.childHookRectTransform;
 230
 78231            referencesContainer.owner = this;
 232
 78233            return referencesContainer as T;
 234        }
 235
 236        IEnumerator LayoutRefreshWatcher()
 237        {
 55238            while (true)
 239            {
 240                // WaitForEndOfFrame doesn't work in batch mode
 1916241                yield return Application.isBatchMode ? null : new WaitForEndOfFrame();
 242
 1794243                if ( !isLayoutDirty )
 244                    continue;
 245
 246                // When running tests this is empty.
 55247                if (!string.IsNullOrEmpty(CommonScriptableObjects.sceneID))
 248                {
 55249                    if (CommonScriptableObjects.sceneID.Get() != scene.sceneData.id)
 250                        continue;
 251                }
 252
 55253                RefreshAll();
 254            }
 255        }
 256
 257        public virtual void RefreshAll()
 258        {
 259            // We are not using the _Internal here because the method is overridden
 260            // by some UI shapes.
 55261            RefreshDCLLayoutRecursively(refreshSize: true, refreshAlignmentAndPosition: false);
 55262            FixMaxStretchRecursively();
 55263            RefreshDCLLayoutRecursively_Internal(refreshSize: false, refreshAlignmentAndPosition: true);
 55264            isLayoutDirty = false;
 55265            OnLayoutRefresh?.Invoke();
 55266            OnLayoutRefresh = null;
 55267        }
 268
 269        public virtual void MarkLayoutDirty( System.Action OnRefresh = null )
 270        {
 93271            UIShape rootParent = GetRootParent();
 272
 93273            Assert.IsTrue(rootParent != null, "root parent must never be null");
 274
 93275            if (rootParent.referencesContainer == null)
 0276                return;
 277
 93278            rootParent.isLayoutDirty = true;
 279
 93280            if ( OnRefresh != null )
 24281                rootParent.OnLayoutRefresh += OnRefresh;
 93282        }
 283
 284        public void RefreshDCLLayout(bool refreshSize = true, bool refreshAlignmentAndPosition = true)
 285        {
 362286            RectTransform parentRT = referencesContainer.GetComponentInParent<RectTransform>();
 287
 362288            if (refreshSize)
 289            {
 242290                RefreshDCLSize(parentRT);
 291            }
 292
 362293            if (refreshAlignmentAndPosition)
 294            {
 295                // Alignment (Alignment uses size so we should always align AFTER resizing)
 266296                RefreshDCLAlignmentAndPosition(parentRT);
 297            }
 362298        }
 299
 300        protected virtual void RefreshDCLSize(RectTransform parentTransform = null)
 301        {
 226302            if (parentTransform == null)
 0303                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 304
 226305            Model model = (Model) this.model;
 306
 226307            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal,
 308                model.width.GetScaledValue(parentTransform.rect.width));
 226309            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical,
 310                model.height.GetScaledValue(parentTransform.rect.height));
 226311        }
 312
 313        public void RefreshDCLAlignmentAndPosition(RectTransform parentTransform = null)
 314        {
 266315            if (parentTransform == null)
 0316                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 317
 266318            referencesContainer.layoutElement.ignoreLayout = false;
 266319            ConfigureAlignment(referencesContainer.layoutGroup);
 266320            Utils.ForceRebuildLayoutImmediate(parentTransform);
 266321            referencesContainer.layoutElement.ignoreLayout = true;
 322
 266323            Model model = (Model) this.model;
 324
 325            // Reposition
 266326            Vector3 position = Vector3.zero;
 266327            position.x = model.positionX.GetScaledValue(parentTransform.rect.width);
 266328            position.y = model.positionY.GetScaledValue(parentTransform.rect.height);
 329
 266330            position = Utils.Sanitize(position);
 266331            referencesContainer.layoutElementRT.localPosition += position;
 266332        }
 333
 334        public virtual void RefreshDCLLayoutRecursively(bool refreshSize = true,
 335            bool refreshAlignmentAndPosition = true)
 336        {
 79337            RefreshDCLLayoutRecursively_Internal(refreshSize, refreshAlignmentAndPosition);
 79338        }
 339
 340        public void RefreshDCLLayoutRecursively_Internal(bool refreshSize = true,
 341            bool refreshAlignmentAndPosition = true)
 342        {
 134343            UIShape rootParent = GetRootParent();
 344
 134345            Assert.IsTrue(rootParent != null, "root parent must never be null");
 346
 134347            if (rootParent.referencesContainer == null)
 2348                return;
 349
 132350            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 351                (x) =>
 352                {
 300353                    if (x.owner != null)
 216354                        x.owner.RefreshDCLLayout(refreshSize, refreshAlignmentAndPosition);
 300355                },
 356                rootParent.referencesContainer.transform);
 132357        }
 358
 359        public void FixMaxStretchRecursively()
 360        {
 55361            UIShape rootParent = GetRootParent();
 362
 55363            Assert.IsTrue(rootParent != null, "root parent must never be null");
 364
 55365            if (rootParent.referencesContainer == null)
 1366                return;
 367
 54368            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 369                (x) =>
 370                {
 138371                    if (x.owner != null)
 372                    {
 96373                        x.rectTransform.SetToMaxStretch();
 374                    }
 138375                },
 376                rootParent.referencesContainer.transform);
 54377        }
 378
 379        protected bool ReparentComponent(RectTransform targetTransform, string targetParent)
 380        {
 68381            bool targetParentExists = !string.IsNullOrEmpty(targetParent) &&
 382                                      scene.disposableComponents.ContainsKey(targetParent);
 383
 68384            if (targetParentExists && parentUIComponent == scene.disposableComponents[targetParent])
 385            {
 22386                return false;
 387            }
 388
 46389            if (parentUIComponent != null)
 390            {
 46391                UIReferencesContainer[] parents = referencesContainer.GetComponentsInParent<UIReferencesContainer>(true)
 392
 186393                foreach (var parent in parents)
 394                {
 47395                    if (parent.owner != null)
 396                    {
 47397                        parent.owner.OnChildDetached(parentUIComponent, this);
 398                    }
 399                }
 400            }
 401
 46402            if (targetParentExists)
 403            {
 22404                parentUIComponent = scene.disposableComponents[targetParent] as UIShape;
 22405            }
 406            else
 407            {
 24408                parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 409            }
 410
 46411            targetTransform.SetParent(parentUIComponent.childHookRectTransform, false);
 46412            return true;
 413        }
 414
 415        public UIShape GetRootParent()
 416        {
 290417            UIShape parent = null;
 418
 290419            if (parentUIComponent != null && !(parentUIComponent is UIScreenSpace))
 420            {
 6421                parent = parentUIComponent.GetRootParent();
 6422            }
 423            else
 424            {
 284425                parent = this;
 426            }
 427
 290428            return parent;
 429        }
 430
 431        protected void ConfigureAlignment(LayoutGroup layout)
 432        {
 266433            Model model = (Model) this.model;
 266434            switch (model.vAlign)
 435            {
 436                case "top":
 7437                    switch (model.hAlign)
 438                    {
 439                        case "left":
 2440                            layout.childAlignment = TextAnchor.UpperLeft;
 2441                            break;
 442                        case "right":
 2443                            layout.childAlignment = TextAnchor.UpperRight;
 2444                            break;
 445                        default:
 3446                            layout.childAlignment = TextAnchor.UpperCenter;
 3447                            break;
 448                    }
 449
 450                    break;
 451                case "bottom":
 26452                    switch (model.hAlign)
 453                    {
 454                        case "left":
 8455                            layout.childAlignment = TextAnchor.LowerLeft;
 8456                            break;
 457                        case "right":
 16458                            layout.childAlignment = TextAnchor.LowerRight;
 16459                            break;
 460                        default:
 2461                            layout.childAlignment = TextAnchor.LowerCenter;
 2462                            break;
 463                    }
 464
 465                    break;
 466                default: // center
 233467                    switch (model.hAlign)
 468                    {
 469                        case "left":
 2470                            layout.childAlignment = TextAnchor.MiddleLeft;
 2471                            break;
 472                        case "right":
 2473                            layout.childAlignment = TextAnchor.MiddleRight;
 2474                            break;
 475                        default:
 229476                            layout.childAlignment = TextAnchor.MiddleCenter;
 477                            break;
 478                    }
 479
 480                    break;
 481            }
 229482        }
 483
 484        protected void SetComponentDebugName()
 485        {
 146486            if (referencesContainer == null || model == null)
 487            {
 0488                return;
 489            }
 490
 146491            referencesContainer.name = componentName;
 146492        }
 493
 494        public override void Dispose()
 495        {
 7496            if (childHookRectTransform)
 7497                Utils.SafeDestroy(childHookRectTransform.gameObject);
 498
 7499            screenSize.OnChange -= OnScreenResize;
 500
 7501            if ( layoutRefreshWatcher != null )
 7502                CoroutineStarter.Stop(layoutRefreshWatcher);
 503
 7504            base.Dispose();
 7505        }
 506
 115507        public virtual void OnChildAttached(UIShape parentComponent, UIShape childComponent) { }
 508
 38509        public virtual void OnChildDetached(UIShape parentComponent, UIShape childComponent) { }
 510    }
 511}