< Summary

Class:DCL.Components.UIValue
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/UI/UIShape.cs
Covered lines:4
Uncovered lines:10
Coverable lines:14
Total lines:508
Line coverage:28.5% (4 of 14)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SetPixels(...)0%2100%
SetPercent(...)0%2100%
UIValue(...)0%2100%
GetScaledValue(...)0%3.073080%

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        {
 024            this.type = Unit.PIXELS;
 025            this.value = value;
 026        }
 27
 28        public void SetPercent(float value)
 29        {
 030            this.type = Unit.PERCENT;
 031            this.value = value;
 032        }
 33
 34        public UIValue(float value, Unit unitType = Unit.PIXELS)
 35        {
 036            this.value = value;
 037            this.type = unitType;
 038        }
 39
 40        public float GetScaledValue(float parentSize)
 41        {
 101242            if (type == Unit.PIXELS)
 91343                return value;
 44
 45            // Prevent division by zero
 9946            if (parentSize <= Mathf.Epsilon)
 047                parentSize = 1;
 48
 9949            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;
 135            public bool visible = true;
 136            public float opacity = 1f;
 137            public string hAlign = "center";
 138            public string vAlign = "center";
 139            public UIValue width = new UIValue(100f);
 140            public UIValue height = new UIValue(50f);
 141            public UIValue positionX = new UIValue(0f);
 142            public UIValue positionY = new UIValue(0f);
 143            public bool isPointerBlocker = true;
 144            public string onClick;
 145
 146            public override BaseModel GetDataFromJSON(string json) { return Utils.SafeFromJson<Model>(json); }
 147        }
 148
 149        public override string componentName => GetDebugName();
 150        public virtual string referencesContainerPrefabName => "";
 151        public UIReferencesContainer referencesContainer;
 152        public RectTransform childHookRectTransform;
 153
 154        public bool isLayoutDirty { get; protected set; }
 155        protected System.Action OnLayoutRefresh;
 156
 157        private BaseVariable<Vector2Int> screenSize => DataStore.i.screen.size;
 158
 159        public UIShape parentUIComponent { get; protected set; }
 160
 161        private Coroutine layoutRefreshWatcher;
 162
 163        public UIShape()
 164        {
 165            screenSize.OnChange += OnScreenResize;
 166            model = new Model();
 167
 168            if ( layoutRefreshWatcher == null )
 169                layoutRefreshWatcher = CoroutineStarter.Start(LayoutRefreshWatcher());
 170        }
 171
 172        private void OnScreenResize(Vector2Int current, Vector2Int previous) => RefreshAll();
 173
 174        public override int GetClassId() { return (int) CLASS_ID.UI_IMAGE_SHAPE; }
 175
 176        public string GetDebugName()
 177        {
 178            Model model = (Model) this.model;
 179
 180            if (string.IsNullOrEmpty(model.name))
 181            {
 182                return GetType().Name;
 183            }
 184            else
 185            {
 186                return GetType().Name + " - " + model.name;
 187            }
 188        }
 189
 190        public override IEnumerator ApplyChanges(BaseModel newJson) { return null; }
 191
 192        internal T InstantiateUIGameObject<T>(string prefabPath) where T : UIReferencesContainer
 193        {
 194            Model model = (Model) this.model;
 195
 196            GameObject uiGameObject = null;
 197
 198            bool targetParentExists = !string.IsNullOrEmpty(model.parentComponent) &&
 199                                      scene.disposableComponents.ContainsKey(model.parentComponent);
 200
 201            if (targetParentExists)
 202            {
 203                if (scene.disposableComponents.ContainsKey(model.parentComponent))
 204                {
 205                    parentUIComponent = (scene.disposableComponents[model.parentComponent] as UIShape);
 206                }
 207                else
 208                {
 209                    parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 210                }
 211            }
 212            else
 213            {
 214                parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 215            }
 216
 217            uiGameObject =
 218                Object.Instantiate(
 219                    Resources.Load(prefabPath),
 220                    parentUIComponent != null ? parentUIComponent.childHookRectTransform : null) as GameObject;
 221
 222            referencesContainer = uiGameObject.GetComponent<T>();
 223
 224            referencesContainer.rectTransform.SetToMaxStretch();
 225
 226            childHookRectTransform = referencesContainer.childHookRectTransform;
 227
 228            referencesContainer.owner = this;
 229
 230            return referencesContainer as T;
 231        }
 232
 233        IEnumerator LayoutRefreshWatcher()
 234        {
 235            while (true)
 236            {
 237                // WaitForEndOfFrame doesn't work in batch mode
 238                yield return Application.isBatchMode ? null : new WaitForEndOfFrame();
 239
 240                if ( !isLayoutDirty )
 241                    continue;
 242
 243                // When running tests this is empty.
 244                if (!string.IsNullOrEmpty(CommonScriptableObjects.sceneID))
 245                {
 246                    if (CommonScriptableObjects.sceneID.Get() != scene.sceneData.id)
 247                        continue;
 248                }
 249
 250                RefreshAll();
 251            }
 252        }
 253
 254        public virtual void RefreshAll()
 255        {
 256            // We are not using the _Internal here because the method is overridden
 257            // by some UI shapes.
 258            RefreshDCLLayoutRecursively(refreshSize: true, refreshAlignmentAndPosition: false);
 259            FixMaxStretchRecursively();
 260            RefreshDCLLayoutRecursively_Internal(refreshSize: false, refreshAlignmentAndPosition: true);
 261            isLayoutDirty = false;
 262            OnLayoutRefresh?.Invoke();
 263            OnLayoutRefresh = null;
 264        }
 265
 266        public virtual void MarkLayoutDirty( System.Action OnRefresh = null )
 267        {
 268            UIShape rootParent = GetRootParent();
 269
 270            Assert.IsTrue(rootParent != null, "root parent must never be null");
 271
 272            if (rootParent.referencesContainer == null)
 273                return;
 274
 275            rootParent.isLayoutDirty = true;
 276
 277            if ( OnRefresh != null )
 278                rootParent.OnLayoutRefresh += OnRefresh;
 279        }
 280
 281        public void RefreshDCLLayout(bool refreshSize = true, bool refreshAlignmentAndPosition = true)
 282        {
 283            RectTransform parentRT = referencesContainer.GetComponentInParent<RectTransform>();
 284
 285            if (refreshSize)
 286            {
 287                RefreshDCLSize(parentRT);
 288            }
 289
 290            if (refreshAlignmentAndPosition)
 291            {
 292                // Alignment (Alignment uses size so we should always align AFTER resizing)
 293                RefreshDCLAlignmentAndPosition(parentRT);
 294            }
 295        }
 296
 297        protected virtual void RefreshDCLSize(RectTransform parentTransform = null)
 298        {
 299            if (parentTransform == null)
 300                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 301
 302            Model model = (Model) this.model;
 303
 304            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal,
 305                model.width.GetScaledValue(parentTransform.rect.width));
 306            referencesContainer.layoutElementRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical,
 307                model.height.GetScaledValue(parentTransform.rect.height));
 308        }
 309
 310        public void RefreshDCLAlignmentAndPosition(RectTransform parentTransform = null)
 311        {
 312            if (parentTransform == null)
 313                parentTransform = referencesContainer.GetComponentInParent<RectTransform>();
 314
 315            referencesContainer.layoutElement.ignoreLayout = false;
 316            ConfigureAlignment(referencesContainer.layoutGroup);
 317            Utils.ForceRebuildLayoutImmediate(parentTransform);
 318            referencesContainer.layoutElement.ignoreLayout = true;
 319
 320            Model model = (Model) this.model;
 321
 322            // Reposition
 323            Vector3 position = Vector3.zero;
 324            position.x = model.positionX.GetScaledValue(parentTransform.rect.width);
 325            position.y = model.positionY.GetScaledValue(parentTransform.rect.height);
 326
 327            position = Utils.Sanitize(position);
 328            referencesContainer.layoutElementRT.localPosition += position;
 329        }
 330
 331        public virtual void RefreshDCLLayoutRecursively(bool refreshSize = true,
 332            bool refreshAlignmentAndPosition = true)
 333        {
 334            RefreshDCLLayoutRecursively_Internal(refreshSize, refreshAlignmentAndPosition);
 335        }
 336
 337        public void RefreshDCLLayoutRecursively_Internal(bool refreshSize = true,
 338            bool refreshAlignmentAndPosition = true)
 339        {
 340            UIShape rootParent = GetRootParent();
 341
 342            Assert.IsTrue(rootParent != null, "root parent must never be null");
 343
 344            if (rootParent.referencesContainer == null)
 345                return;
 346
 347            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 348                (x) =>
 349                {
 350                    if (x.owner != null)
 351                        x.owner.RefreshDCLLayout(refreshSize, refreshAlignmentAndPosition);
 352                },
 353                rootParent.referencesContainer.transform);
 354        }
 355
 356        public void FixMaxStretchRecursively()
 357        {
 358            UIShape rootParent = GetRootParent();
 359
 360            Assert.IsTrue(rootParent != null, "root parent must never be null");
 361
 362            if (rootParent.referencesContainer == null)
 363                return;
 364
 365            Utils.InverseTransformChildTraversal<UIReferencesContainer>(
 366                (x) =>
 367                {
 368                    if (x.owner != null)
 369                    {
 370                        x.rectTransform.SetToMaxStretch();
 371                    }
 372                },
 373                rootParent.referencesContainer.transform);
 374        }
 375
 376        protected bool ReparentComponent(RectTransform targetTransform, string targetParent)
 377        {
 378            bool targetParentExists = !string.IsNullOrEmpty(targetParent) &&
 379                                      scene.disposableComponents.ContainsKey(targetParent);
 380
 381            if (targetParentExists && parentUIComponent == scene.disposableComponents[targetParent])
 382            {
 383                return false;
 384            }
 385
 386            if (parentUIComponent != null)
 387            {
 388                UIReferencesContainer[] parents = referencesContainer.GetComponentsInParent<UIReferencesContainer>(true)
 389
 390                foreach (var parent in parents)
 391                {
 392                    if (parent.owner != null)
 393                    {
 394                        parent.owner.OnChildDetached(parentUIComponent, this);
 395                    }
 396                }
 397            }
 398
 399            if (targetParentExists)
 400            {
 401                parentUIComponent = scene.disposableComponents[targetParent] as UIShape;
 402            }
 403            else
 404            {
 405                parentUIComponent = scene.GetSharedComponent<UIScreenSpace>();
 406            }
 407
 408            targetTransform.SetParent(parentUIComponent.childHookRectTransform, false);
 409            return true;
 410        }
 411
 412        public UIShape GetRootParent()
 413        {
 414            UIShape parent = null;
 415
 416            if (parentUIComponent != null && !(parentUIComponent is UIScreenSpace))
 417            {
 418                parent = parentUIComponent.GetRootParent();
 419            }
 420            else
 421            {
 422                parent = this;
 423            }
 424
 425            return parent;
 426        }
 427
 428        protected void ConfigureAlignment(LayoutGroup layout)
 429        {
 430            Model model = (Model) this.model;
 431            switch (model.vAlign)
 432            {
 433                case "top":
 434                    switch (model.hAlign)
 435                    {
 436                        case "left":
 437                            layout.childAlignment = TextAnchor.UpperLeft;
 438                            break;
 439                        case "right":
 440                            layout.childAlignment = TextAnchor.UpperRight;
 441                            break;
 442                        default:
 443                            layout.childAlignment = TextAnchor.UpperCenter;
 444                            break;
 445                    }
 446
 447                    break;
 448                case "bottom":
 449                    switch (model.hAlign)
 450                    {
 451                        case "left":
 452                            layout.childAlignment = TextAnchor.LowerLeft;
 453                            break;
 454                        case "right":
 455                            layout.childAlignment = TextAnchor.LowerRight;
 456                            break;
 457                        default:
 458                            layout.childAlignment = TextAnchor.LowerCenter;
 459                            break;
 460                    }
 461
 462                    break;
 463                default: // center
 464                    switch (model.hAlign)
 465                    {
 466                        case "left":
 467                            layout.childAlignment = TextAnchor.MiddleLeft;
 468                            break;
 469                        case "right":
 470                            layout.childAlignment = TextAnchor.MiddleRight;
 471                            break;
 472                        default:
 473                            layout.childAlignment = TextAnchor.MiddleCenter;
 474                            break;
 475                    }
 476
 477                    break;
 478            }
 479        }
 480
 481        protected void SetComponentDebugName()
 482        {
 483            if (referencesContainer == null || model == null)
 484            {
 485                return;
 486            }
 487
 488            referencesContainer.name = componentName;
 489        }
 490
 491        public override void Dispose()
 492        {
 493            if (childHookRectTransform)
 494                Utils.SafeDestroy(childHookRectTransform.gameObject);
 495
 496            screenSize.OnChange -= OnScreenResize;
 497
 498            if ( layoutRefreshWatcher != null )
 499                CoroutineStarter.Stop(layoutRefreshWatcher);
 500
 501            base.Dispose();
 502        }
 503
 504        public virtual void OnChildAttached(UIShape parentComponent, UIShape childComponent) { }
 505
 506        public virtual void OnChildDetached(UIShape parentComponent, UIShape childComponent) { }
 507    }
 508}