Search Unity

Nested Scrollrect?

Discussion in 'Unity UI (uGUI) & TextMesh Pro' started by ssawyer, Sep 15, 2014.

  1. ssawyer

    ssawyer

    Joined:
    Sep 15, 2014
    Posts:
    4
    I'm hoping to put together a Netflix-like interface for users to select the scenes in my project.

    I need to be able to scroll up and down overall, but also be able to scroll rows of buttons left and right.


    I can aaaaaalmost get this behavior without much work at all - I simply set up a scrollrect according to the documentation that covers the entire screen, and then made several smaller scrollrects for the horizontal rows of buttons. I made all of the buttons children of the same parent, placed the parent under the overall scrollrect in the hierarchy, and dragged that parent into the Content property of the overall scrollrect. Hopefully that makes sense.



    I can scroll up and down if I touch above, below, or inbetween rows of buttons, and I can scroll horizontally if I touch the buttons. Unfortunately, I can't scroll up and down while touching the buttons. Since the buttons take up the majority of the screenspace, it's definitely an issue.

    Is there something I've overlooked? Is there a better way to set this up? Is this possible in the current state of the UI system?
     
    Last edited: Sep 15, 2014
    IgorAherne, elfjman and rakkarage like this.
  2. Nanity

    Nanity

    Joined:
    Jul 6, 2012
    Posts:
    148
    I heared you can hook into the EventSystem and assing the MouseWheel-Input to the ScrollOverall component only and prevent it from getting grabbed. Could be for 5.0 though.
     
    ssawyer likes this.
  3. ssawyer

    ssawyer

    Joined:
    Sep 15, 2014
    Posts:
    4
    That actually sounds pretty cool! Unfortunately it doesn't help me all that much for this project - I should have mentioned that I'm targeting tablets.
     
  4. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,093
    Hi, in this case I would have it so the horizontal scroll views handle the scrolling. If you scroll up / down I would then pass the scroll value to the parent scroll view to do the vertical scrolling.
     
  5. ssawyer

    ssawyer

    Joined:
    Sep 15, 2014
    Posts:
    4
    I tried this using "verticalNormalizedPosition" without much luck. The horizontal scrollrects would pass either a 1 or 0, since their content was the same size as the scrollrect.

    Was there a different method you meant I should try? Thanks.
     
  6. Adquizition

    Adquizition

    Joined:
    Oct 11, 2014
    Posts:
    13
    I have the exact same problem, and am curious if anyone found a solution.
     
  7. FoWare

    FoWare

    Joined:
    May 24, 2013
    Posts:
    13
    Yep, watching this thread if anyone can confirm a reliable solution to nesting scroll rects and getting smooth/fluid behavior.
     
  8. cowtrix

    cowtrix

    Joined:
    Oct 23, 2012
    Posts:
    279
    This may be a complex enough situation to warrant just implementing the IDrag interfaces yourself.
     
  9. Adquizition

    Adquizition

    Joined:
    Oct 11, 2014
    Posts:
    13
    Hi everyone! So I think I figured out a pretty easy, and I think elegant solution for touch screen devices for this (at least until unity supports this which I hope they will in the future!). I have a simple touch gesture script that sends out gesture events, so if you have a vertical scroll rect and a nested horizontal scroll rect like above, figure out in the OnGesture or whatever you have listening for the gesture if the user is swiping in a vertical motion. If they are, disable the ScrollRect script on the nested scroll rects. It works perfectly for anything that scrolls the opposite of the parent, at least. I tried using IDrag interfaces but there were too many issues I encountered, and I really wanted a way to not recreate what unity built with the scroll rects in the first place. If anyone has an even better solution, I'm all ears :)
     
    JohnTube likes this.
  10. CaptainSchnittchen

    CaptainSchnittchen

    Joined:
    Feb 15, 2014
    Posts:
    1
    For me subclassing ScrollRect worked. I basically route the drag events to all parent ScrollRects in case a simple rule checked in OnBeginDrag is true. For my simple case I just wanted to route events to the parent in case a horizontal drag was being initiated but the scroll rect is a vertical one, or a vertical drag was being initiated and the scroll rect is a horizontal one. This is my subclass:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4. using System;
    5. using UnityEngine.EventSystems;
    6.  
    7. public class ScrollRectEx : ScrollRect {
    8.  
    9.     private bool routeToParent = false;
    10.  
    11.  
    12.     /// <summary>
    13.     /// Do action for all parents
    14.     /// </summary>
    15.     private void DoForParents<T>(Action<T> action) where T:IEventSystemHandler
    16.     {
    17.         Transform parent = transform.parent;
    18.         while(parent != null) {
    19.             foreach(var component in parent.GetComponents<Component>()) {
    20.                 if(component is T)
    21.                     action((T)(IEventSystemHandler)component);
    22.             }
    23.             parent = parent.parent;
    24.         }
    25.     }
    26.  
    27.     /// <summary>
    28.     /// Always route initialize potential drag event to parents
    29.     /// </summary>
    30.     public override void OnInitializePotentialDrag (PointerEventData eventData)
    31.     {
    32.         DoForParents<IInitializePotentialDragHandler>((parent) => { parent.OnInitializePotentialDrag(eventData); });
    33.         base.OnInitializePotentialDrag (eventData);
    34.     }
    35.  
    36.     /// <summary>
    37.     /// Drag event
    38.     /// </summary>
    39.     public override void OnDrag (UnityEngine.EventSystems.PointerEventData eventData)
    40.     {
    41.         if(routeToParent)
    42.             DoForParents<IDragHandler>((parent) => { parent.OnDrag(eventData); });
    43.         else
    44.             base.OnDrag (eventData);
    45.     }
    46.  
    47.     /// <summary>
    48.     /// Begin drag event
    49.     /// </summary>
    50.     public override void OnBeginDrag (UnityEngine.EventSystems.PointerEventData eventData)
    51.     {
    52.         if(!horizontal && Math.Abs (eventData.delta.x) > Math.Abs (eventData.delta.y))
    53.             routeToParent = true;
    54.         else if(!vertical && Math.Abs (eventData.delta.x) < Math.Abs (eventData.delta.y))
    55.             routeToParent = true;
    56.         else
    57.             routeToParent = false;
    58.  
    59.         if(routeToParent)
    60.             DoForParents<IBeginDragHandler>((parent) => { parent.OnBeginDrag(eventData); });
    61.         else
    62.             base.OnBeginDrag (eventData);
    63.     }
    64.  
    65.     /// <summary>
    66.     /// End drag event
    67.     /// </summary>
    68.     public override void OnEndDrag (UnityEngine.EventSystems.PointerEventData eventData)
    69.     {
    70.         if(routeToParent)
    71.             DoForParents<IEndDragHandler>((parent) => { parent.OnEndDrag(eventData); });
    72.         else
    73.             base.OnEndDrag (eventData);
    74.         routeToParent = false;
    75.     }
    76. }
    77.  
    Hope this helps! Cheers.
     
  11. Adquizition

    Adquizition

    Joined:
    Oct 11, 2014
    Posts:
    13
    This does help! Way better than my solution. Thanks!
     
  12. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,629
    Nice solution @CaptainSchnittchen, fancy adding that to the UI Extensions (link in sig) bitbucket repo? (or let me know you are ok with it and I'll add it :D)
     
    benzsuankularb likes this.
  13. GamesRUs

    GamesRUs

    Joined:
    Apr 8, 2012
    Posts:
    17
    If you want to be able to use just scrolling with a touch pad, I find that the following method is needed to support that situation. Perhaps I am doing something wrong, but at least this works for me and might help someone else?

    public override void OnScroll (PointerEventData eventData)
    {
    if (!horizontal && Math.Abs (eventData.scrollDelta.x) > Math.Abs (eventData.scrollDelta.y)) {
    routeToParent = true;
    } else if (!vertical && Math.Abs (eventData.scrollDelta.x) < Math.Abs (eventData.scrollDelta.y)) {
    routeToParent = true;
    } else
    routeToParent = false;

    if (routeToParent)
    DoForParents<IScrollHandler> ((parent) => {
    parent.OnScroll (eventData); });
    else
    base.OnScroll (eventData);
    }
     
  14. NetyaginSergey1

    NetyaginSergey1

    Joined:
    Jan 2, 2015
    Posts:
    3
    Hi, Ssawyer! I have a similar task, but I couldn't solve it. I need to make the scroll vertical menu. But it is necessary that each element of this menu could be also scrolled across (horizontal). At the same time on the screen at me only one element of the menu - the others outside visibility area. But at you, seemingly, everything well turned out. You completely could open hierarchy and show, what components at you are attached to each element? Do You could open hierarchy and show, what components at you are attached to each element? (For example, it is my hierarchy)...

    Screenshoot.jpg

    Best regards!
    Sergey.
     
  15. NetyaginSergey1

    NetyaginSergey1

    Joined:
    Jan 2, 2015
    Posts:
    3
    I have also one little problem with rendering of text component. My Text component based on Canvas. And i have simple 3D-object near camera. And... all element of menu rendered normal, but title text rendered above 3D-object (Text component located on same layer and z-position? as his parent objects).

    upload_2015-10-7_23-12-44.png
     

    Attached Files:

  16. Polymorphik

    Polymorphik

    Joined:
    Jul 25, 2014
    Posts:
    599
    Oh man this helped me out A LOT, expect that after I scroll it doesn't lerp. Is there something blocking it from using Inertia?
     
  17. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    411


    THAT

    IS

    F***ING

    IMPRESSIVE.
     
  18. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    411
    Merely your

    DoForParents

    class is incredibly beautiful.

    Wow. Holy crap.
     
  19. OJ3D

    OJ3D

    Joined:
    Feb 13, 2014
    Posts:
    33
    CaptainSchnittchen - Holy F-ING Cow! YOU KICK ASS!
    This was pretty much a deal breaker in my app because it seriously hinders the user experience.
    Thanks for the share.
     
  20. RossMelbourne

    RossMelbourne

    Joined:
    Dec 9, 2013
    Posts:
    2
    CaptainSchnittchen - A huge thank you!
     
  21. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    411
    seriously .. CaptainSchnittchen this is like the best post on the whole site. Cheers again
     
  22. wmadwand

    wmadwand

    Joined:
    Jul 19, 2014
    Posts:
    13
    CaptainSchnittchen, i shake your hand! Thanks a lot for your incredibly graceful solution!
    If i had found it out by myself i definitely could've been the greatest brain ever.
    Seems like you are one of Unity's evangelist, aren't you? ;) If not - you should be!
    Anyway, thank you thousand times and wish you all the best!
     
  23. HankHongChen

    HankHongChen

    Joined:
    Jan 19, 2016
    Posts:
    2
    CaptainSchnittchen,
    Thanks a lot man, help me A LOT~~
     
  24. zijuan0810

    zijuan0810

    Joined:
    Apr 23, 2016
    Posts:
    1
    CaptainSchnittchen
    Thank you very much, u way is greay!
     
  25. mLorite

    mLorite

    Joined:
    Nov 26, 2015
    Posts:
    1
    thank you! CaptainSchnittchen
     
    Last edited: May 12, 2016
  26. imandic

    imandic

    Joined:
    Mar 6, 2014
    Posts:
    31
    Thanks CaptainSchnittchen!
     
  27. Sorobaid

    Sorobaid

    Joined:
    Aug 31, 2012
    Posts:
    13
    Holy S*** CaptainSchnittchen! Can really kiss you now ;)
     
  28. Bibzball

    Bibzball

    Joined:
    Sep 26, 2015
    Posts:
    20
    Dear CaptainSchnittchen,

    From the bottom of my heart, thank you very much :D
    Awesome solution.
     
  29. jesusrg

    jesusrg

    Joined:
    Dec 12, 2012
    Posts:
    27
    Perfect solution CaptainSchnittchen, thank you very much!!!!
     
  30. D3ntrax

    D3ntrax

    Joined:
    Mar 21, 2014
    Posts:
    35
  31. Excadrix13

    Excadrix13

    Joined:
    Mar 8, 2016
    Posts:
    2
    2 years later still this solution ROCKS!!!!
     
  32. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    341
  33. TheCodeTroll

    TheCodeTroll

    Joined:
    Mar 26, 2013
    Posts:
    4
    Another person adding to the long list of people praising @CaptainSchnittchen
    Thank you for the elegant solution.
    I was going down the route of subclassing ScrollRect anyway and stumbled upon this, massive help in understanding the events :)
     
  34. Hotshot10101

    Hotshot10101

    Joined:
    Jun 23, 2012
    Posts:
    112
    Works awesome. Thanks for the post.
     
  35. RVieytes

    RVieytes

    Joined:
    Jan 11, 2017
    Posts:
    1
    THANKS A LOT!!!
    Totally worked. I love the simplicity of the code too, really elegant. :D
     
  36. khalileds

    khalileds

    Joined:
    Jan 12, 2017
    Posts:
    1
    Hi, in this case I would have it so the horizontal scroll views handle the scrolling. If you scroll up / down I would then pass the scroll value to the parent scroll view to do the vertical scrolling.
     
    Last edited: Mar 26, 2017
  37. XiongGuiYang

    XiongGuiYang

    Joined:
    Sep 5, 2016
    Posts:
    14
    Layout1.gif
     

    Attached Files:

  38. metomero

    metomero

    Joined:
    Aug 18, 2016
    Posts:
    1
  39. KGC

    KGC

    Joined:
    Oct 2, 2014
    Posts:
    11
    Here's my take on the solution by @CaptainSchnittchen. I wanted to get rid of the upwards traversal of the hierarchy, as our project will run on mobile devices with limited processing power. So i got rid of that by letting the developer assign up to 1 ScrollRect as parent, and delegate the event as the original solution, but without using the System.Action delegate pattern (in order to avoid garbage generation). My solution consists of 2 files, the ScrollRectNested component and the ScrollRectNestedEditor editor class. The editor is needed as I'm adding a new property and the standard ScrollRectEditor wont draw it by default.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4. using System;
    5. using UnityEngine.EventSystems;
    6.  
    7. /// <summary>
    8. /// ScrollRect that supports being nested inside another ScrollRect.
    9. /// BASED ON: https://forum.unity3d.com/threads/nested-scrollrect.268551/#post-1906953
    10. /// </summary>
    11. public class ScrollRectNested : ScrollRect
    12. {
    13.     [Header("Additional Fields")]
    14.     [SerializeField]
    15.     ScrollRect parentScrollRect;
    16.  
    17.     bool routeToParent = false;
    18.  
    19.     public override void OnInitializePotentialDrag(PointerEventData eventData)
    20.     {
    21.         // Always route initialize potential drag event to parent
    22.         if (parentScrollRect != null)
    23.         {
    24.             ((IInitializePotentialDragHandler)parentScrollRect).OnInitializePotentialDrag(eventData);
    25.         }
    26.         base.OnInitializePotentialDrag(eventData);
    27.     }
    28.  
    29.     public override void OnDrag(UnityEngine.EventSystems.PointerEventData eventData)
    30.     {
    31.         if (routeToParent)
    32.         {
    33.             if (parentScrollRect != null)
    34.             {
    35.                 ((IDragHandler)parentScrollRect).OnDrag(eventData);
    36.             }
    37.         }
    38.         else
    39.         {
    40.             base.OnDrag(eventData);
    41.         }
    42.     }
    43.  
    44.     public override void OnBeginDrag(UnityEngine.EventSystems.PointerEventData eventData)
    45.     {
    46.         if (!horizontal && Math.Abs(eventData.delta.x) > Math.Abs(eventData.delta.y))
    47.         {
    48.             routeToParent = true;
    49.         }
    50.         else if (!vertical && Math.Abs(eventData.delta.x) < Math.Abs(eventData.delta.y))
    51.         {
    52.             routeToParent = true;
    53.         }
    54.         else
    55.         {
    56.             routeToParent = false;
    57.         }
    58.  
    59.         if (routeToParent)
    60.         {
    61.             if (parentScrollRect != null)
    62.             {
    63.                 ((IBeginDragHandler)parentScrollRect).OnBeginDrag(eventData);
    64.             }
    65.         }
    66.         else
    67.         {
    68.             base.OnBeginDrag(eventData);
    69.         }
    70.     }
    71.  
    72.     public override void OnEndDrag(UnityEngine.EventSystems.PointerEventData eventData)
    73.     {
    74.         if (routeToParent)
    75.         {
    76.             if (parentScrollRect != null)
    77.             {
    78.                 ((IEndDragHandler)parentScrollRect).OnEndDrag(eventData);
    79.             }
    80.         }
    81.         else
    82.         {
    83.             base.OnEndDrag(eventData);
    84.         }
    85.         routeToParent = false;
    86.     }
    87. }
    88.  
    89.  

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using UnityEngine.UI;
    6. using UnityEditor.UI;
    7.  
    8. [CustomEditor(typeof(ScrollRectNested))]
    9. public class ScrollRectNestedEditor : ScrollRectEditor
    10. {
    11.     SerializedProperty parentScrollRectProp;
    12.     GUIContent parentScrollRectGUIContent = new GUIContent("Parent ScrollRect");
    13.  
    14.     protected override void OnEnable()
    15.     {
    16.         base.OnEnable();
    17.         parentScrollRectProp = serializedObject.FindProperty("parentScrollRect");
    18.     }
    19.  
    20.     public override void OnInspectorGUI()
    21.     {
    22.         base.OnInspectorGUI();
    23.         serializedObject.Update();
    24.         EditorGUILayout.PropertyField(parentScrollRectProp, parentScrollRectGUIContent);
    25.         serializedObject.ApplyModifiedProperties();
    26.     }
    27. }
    28.  
    29.  
     
  40. jrcharland

    jrcharland

    Joined:
    Dec 19, 2014
    Posts:
    2
    Even if it's been 2 years since that post from @CaptainSchnittchen, I just had to say thank you ! Elegant !
     
    Mycroft likes this.
  41. abusaad

    abusaad

    Joined:
    May 14, 2013
    Posts:
    4
  42. disny1234

    disny1234

    Joined:
    Sep 21, 2014
    Posts:
    21
  43. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,629
    Nice alternate solution @KGC , this would be another good addition to the Unity UI Extensions project!
     
  44. Gastaldello

    Gastaldello

    Joined:
    Oct 12, 2016
    Posts:
    1
  45. benzsuankularb

    benzsuankularb

    Joined:
    Apr 10, 2013
    Posts:
    129
    Did you have one in your repo?
     
  46. RahulOfTheRamanEffect

    RahulOfTheRamanEffect

    Joined:
    Feb 16, 2015
    Posts:
    5
    I stumbled upon this thread recently while trying to find a way to nest my ScrollRects. The solution by @CaptainSchnittchen (and its revision by @KGC) was absolutely great, but I found that I needed to modify it further to do the following:

    • Allow events to be passed up the hierarchy to any kind of parent container (maybe a custom class that consumes any or all of the input events)
    • Handle OnScroll (@GamesRUs caught on to this)
    • If an event is unused, forward it up the hierarchy
      • Example 01: If my content RectTransform is smaller than my viewport, then any drag/scroll event is not used, so forward the event up the hierarchy
      • Example 02: If I try to scroll up on a Scrollrect that is already fully scrolled up, then forward the event up the hierarchy
    • Improve performance: Cache the relevant container components. Update them only when the Transform Parent has been changed.
    All of this led me to build a custom ScrollRect component based on the original code from the UnityEngine.UI namespace and the solutions on this thread.


    Usage:
    1. Set it up as like you would a regular ScrollRect.
    2. Tick "Forward Unused Events To Container" if you'd like to send events up the hierarchy.

    You can copy the class and its editor script here (or download the attachment below):

    BaseScrollRect.cs
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.Events;
    4. using UnityEngine.EventSystems;
    5. using UnityEngine.UI;
    6.  
    7. namespace TheRamanEffect.Unity.UI
    8. {
    9.     /// <summary>
    10.     /// ScrollRect that forwards unused events up to containing objects.
    11.     /// BASED ON: The original ScrollRect component supplied by Unity in the UnityEngine.UI namespace
    12.     /// INSPIRED BY: https://forum.unity3d.com/threads/nested-scrollrect.268551/#post-1906953
    13.     /// </summary>
    14.     public class BaseScrollRect : BaseUIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup
    15.     {
    16.         public enum MovementType
    17.         {
    18.             Unrestricted, // Unrestricted movement -- can scroll forever
    19.             Elastic, // Restricted but flexible -- can go past the edges, but springs back in place
    20.             Clamped, // Restricted movement where it's not possible to go past the edges
    21.         }
    22.  
    23.         public enum ScrollbarVisibility
    24.         {
    25.             Permanent,
    26.             AutoHide,
    27.             AutoHideAndExpandViewport,
    28.         }
    29.  
    30.         [Serializable]
    31.         public class ScrollRectEvent : UnityEvent<Vector2> { }
    32.  
    33.         [SerializeField]
    34.         private RectTransform content;
    35.         public RectTransform Content
    36.         {
    37.             get { return content; }
    38.             set { content = value; }
    39.         }
    40.  
    41.         [SerializeField]
    42.         private bool horizontallyScrollable = true;
    43.         public bool HorizontallyScrollable
    44.         {
    45.             get { return horizontallyScrollable; }
    46.             set { horizontallyScrollable = value; }
    47.         }
    48.  
    49.         [SerializeField]
    50.         private bool verticallyScrollable = true;
    51.         public bool VerticallyScrollable
    52.         {
    53.             get { return verticallyScrollable; }
    54.             set { verticallyScrollable = value; }
    55.         }
    56.  
    57.         [SerializeField]
    58.         private MovementType scrollingMovementType = MovementType.Elastic;
    59.         public MovementType ScrollingMovementType
    60.         {
    61.             get { return scrollingMovementType; }
    62.             set { scrollingMovementType = value; }
    63.         }
    64.  
    65.         [SerializeField]
    66.         private float elasticity = 0.1f; // Only used for MovementType.Elastic
    67.         public float Elasticity
    68.         {
    69.             get { return elasticity; }
    70.             set { elasticity = value; }
    71.         }
    72.  
    73.         [SerializeField]
    74.         private bool inertia = true;
    75.         public bool Inertia
    76.         {
    77.             get { return inertia; }
    78.             set { inertia = value; }
    79.         }
    80.  
    81.         [SerializeField]
    82.         private float decelerationRate = 0.135f; // Only used when inertia is enabled
    83.         public float DecelerationRate
    84.         {
    85.             get { return decelerationRate; }
    86.             set { decelerationRate = value; }
    87.         }
    88.  
    89.         [SerializeField]
    90.         private float scrollSensitivity = 1.0f;
    91.         public float ScrollSensitivity
    92.         {
    93.             get { return scrollSensitivity; }
    94.             set { scrollSensitivity = value; }
    95.         }
    96.  
    97.         [SerializeField]
    98.         private RectTransform viewportRectTransform;
    99.         public RectTransform ViewportRectTransform
    100.         {
    101.             get { return viewportRectTransform; }
    102.             set { viewportRectTransform = value; SetDirtyCaching(); }
    103.         }
    104.  
    105.         [SerializeField]
    106.         private Scrollbar horizontalScrollbar;
    107.         public Scrollbar HorizontalScrollbar
    108.         {
    109.             get
    110.             {
    111.                 return horizontalScrollbar;
    112.             }
    113.             set
    114.             {
    115.                 if (horizontalScrollbar)
    116.                     horizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
    117.                 horizontalScrollbar = value;
    118.                 if (horizontalScrollbar)
    119.                     horizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
    120.                 SetDirtyCaching();
    121.             }
    122.         }
    123.  
    124.         [SerializeField]
    125.         private Scrollbar verticalScrollbar;
    126.         public Scrollbar VerticalScrollbar
    127.         {
    128.             get
    129.             {
    130.                 return verticalScrollbar;
    131.             }
    132.             set
    133.             {
    134.                 if (verticalScrollbar)
    135.                     verticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
    136.                 verticalScrollbar = value;
    137.                 if (verticalScrollbar)
    138.                     verticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
    139.                 SetDirtyCaching();
    140.             }
    141.         }
    142.  
    143.         [SerializeField]
    144.         private ScrollbarVisibility horizontalScrollbarVisibility;
    145.         public ScrollbarVisibility HorizontalScrollbarVisibilityMode
    146.         {
    147.             get { return horizontalScrollbarVisibility; }
    148.             set { horizontalScrollbarVisibility = value; SetDirtyCaching(); }
    149.         }
    150.  
    151.         [SerializeField]
    152.         private ScrollbarVisibility verticalScrollbarVisibility;
    153.         public ScrollbarVisibility VerticalScrollbarVisibilityMode
    154.         {
    155.             get { return verticalScrollbarVisibility; }
    156.             set { verticalScrollbarVisibility = value; SetDirtyCaching(); }
    157.         }
    158.  
    159.         [SerializeField]
    160.         private float horizontalScrollbarSpacing;
    161.         public float HorizontalScrollbarSpacing
    162.         {
    163.             get { return horizontalScrollbarSpacing; }
    164.             set { horizontalScrollbarSpacing = value; SetDirty(); }
    165.         }
    166.  
    167.         [SerializeField]
    168.         private float verticalScrollbarSpacing;
    169.         public float VerticalScrollbarSpacing
    170.         {
    171.             get { return verticalScrollbarSpacing; }
    172.             set { verticalScrollbarSpacing = value; SetDirty(); }
    173.         }
    174.      
    175.         [SerializeField]
    176.         private bool forwardUnusedEventsToContainer;
    177.         public bool ForwardUnusedEventsToContainer
    178.         {
    179.             get { return forwardUnusedEventsToContainer; }
    180.             set { forwardUnusedEventsToContainer = value; }
    181.         }
    182.  
    183.         [SerializeField]
    184.         private ScrollRectEvent onValueChanged = new ScrollRectEvent();
    185.         public ScrollRectEvent OnValueChanged
    186.         {
    187.             get { return onValueChanged; }
    188.             set { onValueChanged = value; }
    189.         }
    190.  
    191.         // The offset from handle position to mouse down position
    192.         private Vector2 pointerStartLocalCursor = Vector2.zero;
    193.         private Vector2 contentStartPosition = Vector2.zero;
    194.  
    195.         private RectTransform viewRect;
    196.         protected RectTransform ViewRect
    197.         {
    198.             get
    199.             {
    200.                 if (viewRect == null)
    201.                     viewRect = viewportRectTransform;
    202.                 if (viewRect == null)
    203.                     viewRect = (RectTransform)transform;
    204.                 return viewRect;
    205.             }
    206.         }
    207.  
    208.         private Bounds contentBounds;
    209.         private Bounds viewBounds;
    210.  
    211.         private Vector2 velocity;
    212.         public Vector2 Velocity
    213.         {
    214.             get { return velocity; }
    215.             set { velocity = value; }
    216.         }
    217.  
    218.         private bool dragging;
    219.  
    220.         private Vector2 prevPosition = Vector2.zero;
    221.         private Bounds prevContentBounds;
    222.         private Bounds prevViewBounds;
    223.         [NonSerialized]
    224.         private bool hasRebuiltLayout = false;
    225.  
    226.         private bool hSliderExpand;
    227.         private bool vSliderExpand;
    228.         private float hSliderHeight;
    229.         private float vSliderWidth;
    230.  
    231.         [NonSerialized]
    232.         private RectTransform rectTransform;
    233.         private RectTransform RectTransform
    234.         {
    235.             get
    236.             {
    237.                 if (rectTransform == null)
    238.                     rectTransform = GetComponent<RectTransform>();
    239.                 return rectTransform;
    240.             }
    241.         }
    242.  
    243.         private RectTransform horizontalScrollbarRect;
    244.         private RectTransform verticalScrollbarRect;
    245.  
    246.         private DrivenRectTransformTracker tracker;
    247.  
    248.         private IInitializePotentialDragHandler parentInitializePotentialDragHandler;
    249.         private IBeginDragHandler parentBeginDragHandler;
    250.         private IDragHandler parentDragHandler;
    251.         private IEndDragHandler parentEndDragHandler;
    252.         private IScrollHandler parentScrollHandler;
    253.  
    254.         private bool routeToParent;
    255.  
    256.         protected BaseScrollRect()
    257.         {
    258.             flexibleWidth = -1;
    259.         }
    260.  
    261.         public virtual void Rebuild(CanvasUpdate executing)
    262.         {
    263.             if (executing == CanvasUpdate.Prelayout)
    264.             {
    265.                 UpdateCachedData();
    266.             }
    267.  
    268.             if (executing == CanvasUpdate.PostLayout)
    269.             {
    270.                 UpdateBounds();
    271.                 UpdateScrollbars(Vector2.zero);
    272.                 UpdatePrevData();
    273.  
    274.                 hasRebuiltLayout = true;
    275.             }
    276.         }
    277.  
    278.         public virtual void LayoutComplete()
    279.         { }
    280.  
    281.         public virtual void GraphicUpdateComplete()
    282.         { }
    283.  
    284.         private void UpdateCachedData()
    285.         {
    286.             Transform transform = this.transform;
    287.             horizontalScrollbarRect = horizontalScrollbar == null ? null : horizontalScrollbar.transform as RectTransform;
    288.             verticalScrollbarRect = verticalScrollbar == null ? null : verticalScrollbar.transform as RectTransform;
    289.  
    290.             // These are true if either the elements are children, or they don't exist at all.
    291.             bool viewIsChild = (ViewRect.parent == transform);
    292.             bool hScrollbarIsChild = (!horizontalScrollbarRect || horizontalScrollbarRect.parent == transform);
    293.             bool vScrollbarIsChild = (!verticalScrollbarRect || verticalScrollbarRect.parent == transform);
    294.             bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild);
    295.  
    296.             hSliderExpand = allAreChildren && horizontalScrollbarRect && HorizontalScrollbarVisibilityMode == ScrollbarVisibility.AutoHideAndExpandViewport;
    297.             vSliderExpand = allAreChildren && verticalScrollbarRect && VerticalScrollbarVisibilityMode == ScrollbarVisibility.AutoHideAndExpandViewport;
    298.          
    299.             hSliderHeight = (horizontalScrollbarRect == null ? 0 : horizontalScrollbarRect.rect.height);
    300.             vSliderWidth = (verticalScrollbarRect == null ? 0 : verticalScrollbarRect.rect.width);
    301.         }
    302.  
    303.         private T GetComponentOnlyInParents<T>()
    304.         {
    305.             if (transform.parent != null)
    306.                 return transform.parent.GetComponentInParent<T>();
    307.  
    308.             return default(T);
    309.         }
    310.  
    311.         private void CacheParentContainerComponents()
    312.         {
    313.             parentInitializePotentialDragHandler = GetComponentOnlyInParents<IInitializePotentialDragHandler>();
    314.             parentBeginDragHandler = GetComponentOnlyInParents<IBeginDragHandler>();
    315.             parentDragHandler = GetComponentOnlyInParents<IDragHandler>();
    316.             parentEndDragHandler = GetComponentOnlyInParents<IEndDragHandler>();
    317.             parentScrollHandler = GetComponentOnlyInParents<IScrollHandler>();
    318.         }
    319.  
    320.         protected override void OnTransformParentChanged()
    321.         {
    322.             base.OnTransformParentChanged();
    323.             CacheParentContainerComponents();
    324.         }
    325.  
    326.         protected override void OnEnable()
    327.         {
    328.             base.OnEnable();
    329.  
    330.             if (horizontalScrollbar)
    331.                 horizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
    332.             if (verticalScrollbar)
    333.                 verticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
    334.  
    335.             CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
    336.             UpdateCachedData();
    337.             CacheParentContainerComponents();
    338.         }
    339.  
    340.         protected override void OnDisable()
    341.         {
    342.             CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
    343.  
    344.             if (horizontalScrollbar)
    345.                 horizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
    346.             if (verticalScrollbar)
    347.                 verticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
    348.  
    349.             hasRebuiltLayout = false;
    350.             tracker.Clear();
    351.             velocity = Vector2.zero;
    352.             LayoutRebuilder.MarkLayoutForRebuild(RectTransform);
    353.             UpdateCachedData();
    354.             base.OnDisable();
    355.         }
    356.  
    357.         public override bool IsActive()
    358.         {
    359.             return base.IsActive() && content != null;
    360.         }
    361.  
    362.         private void EnsureLayoutHasRebuilt()
    363.         {
    364.             if (!hasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout())
    365.                 Canvas.ForceUpdateCanvases();
    366.         }
    367.  
    368.         public virtual void StopMovement()
    369.         {
    370.             velocity = Vector2.zero;
    371.         }
    372.  
    373.         public virtual void OnInitializePotentialDrag(PointerEventData eventData)
    374.         {
    375.             if (eventData.button != PointerEventData.InputButton.Left)
    376.                 return;
    377.  
    378.             velocity = Vector2.zero;
    379.  
    380.             // Always route initialize potential drag event to parent
    381.             if (parentInitializePotentialDragHandler != null)
    382.                 parentInitializePotentialDragHandler.OnInitializePotentialDrag(eventData);
    383.         }
    384.  
    385.         private void EvaluateRouteToParent(Vector2 delta, bool isXInverted, bool isYInverted)
    386.         {
    387.             routeToParent = false;
    388.  
    389.             if (!forwardUnusedEventsToContainer)
    390.                 return;
    391.  
    392.             if(Math.Abs(delta.x) > Math.Abs(delta.y))
    393.             {
    394.                 if (horizontallyScrollable)
    395.                 {
    396.                     if (!HScrollingNeeded)
    397.                         routeToParent = true;
    398.                     else if (HorizontalNormalizedPosition == 0 && ((delta.x > 0 && isXInverted) || (delta.x < 0 && !isXInverted)))
    399.                         routeToParent = true;
    400.                     else if (HorizontalNormalizedPosition == 1 && ((delta.x < 0 && isXInverted) || (delta.x > 0 && !isXInverted)))
    401.                         routeToParent = true;
    402.                 }
    403.                 else
    404.                     routeToParent = true;
    405.             }
    406.             else if (Math.Abs(delta.x) < Math.Abs(delta.y))
    407.             {
    408.                 if (verticallyScrollable)
    409.                 {
    410.                     if (!VScrollingNeeded)
    411.                         routeToParent = true;
    412.                     else if (VerticalNormalizedPosition == 0 && ((delta.y > 0 && isYInverted) || (delta.y < 0 && !isYInverted)))
    413.                         routeToParent = true;
    414.                     else if (VerticalNormalizedPosition == 1 && ((delta.y < 0 && isYInverted) || (delta.y > 0 && !isYInverted)))
    415.                         routeToParent = true;
    416.                 }
    417.                 else
    418.                     routeToParent = true;
    419.             }
    420.         }
    421.  
    422.         public virtual void OnBeginDrag(PointerEventData eventData)
    423.         {
    424.             EvaluateRouteToParent(eventData.delta, true, true);
    425.  
    426.             if (routeToParent && parentBeginDragHandler != null)
    427.                 parentBeginDragHandler.OnBeginDrag(eventData);
    428.             else
    429.             {
    430.                 if (eventData.button != PointerEventData.InputButton.Left)
    431.                     return;
    432.  
    433.                 if (!IsActive())
    434.                     return;
    435.  
    436.                 UpdateBounds();
    437.  
    438.                 pointerStartLocalCursor = Vector2.zero;
    439.                 RectTransformUtility.ScreenPointToLocalPointInRectangle(ViewRect, eventData.position, eventData.pressEventCamera, out pointerStartLocalCursor);
    440.                 contentStartPosition = content.anchoredPosition;
    441.                 dragging = true;
    442.             }
    443.         }
    444.  
    445.         public virtual void OnEndDrag(PointerEventData eventData)
    446.         {
    447.             if (routeToParent && parentEndDragHandler != null)
    448.                 parentEndDragHandler.OnEndDrag(eventData);
    449.             else
    450.             {
    451.                 if (eventData.button != PointerEventData.InputButton.Left)
    452.                     return;
    453.  
    454.                 dragging = false;
    455.             }
    456.  
    457.             routeToParent = false;
    458.         }
    459.  
    460.         public virtual void OnDrag(PointerEventData eventData)
    461.         {
    462.             if (routeToParent && parentDragHandler != null)
    463.                 parentDragHandler.OnDrag(eventData);
    464.             else
    465.             {
    466.                 if (eventData.button != PointerEventData.InputButton.Left)
    467.                     return;
    468.  
    469.                 if (!IsActive())
    470.                     return;
    471.  
    472.                 Vector2 localCursor;
    473.                 if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(ViewRect, eventData.position, eventData.pressEventCamera, out localCursor))
    474.                     return;
    475.  
    476.                 UpdateBounds();
    477.  
    478.                 var pointerDelta = localCursor - pointerStartLocalCursor;
    479.                 Vector2 position = contentStartPosition + pointerDelta;
    480.  
    481.                 // Offset to get content into place in the view.
    482.                 Vector2 offset = CalculateOffset(position - content.anchoredPosition);
    483.                 position += offset;
    484.                 if (scrollingMovementType == MovementType.Elastic)
    485.                 {
    486.                     if (offset.x != 0)
    487.                         position.x = position.x - RubberDelta(offset.x, viewBounds.size.x);
    488.                     if (offset.y != 0)
    489.                         position.y = position.y - RubberDelta(offset.y, viewBounds.size.y);
    490.                 }
    491.  
    492.                 SetContentAnchoredPosition(position);
    493.             }
    494.         }
    495.  
    496.         public virtual void OnScroll(PointerEventData eventData)
    497.         {
    498.             EvaluateRouteToParent(eventData.scrollDelta, false, false);
    499.  
    500.             if (routeToParent && parentScrollHandler != null)
    501.                 parentScrollHandler.OnScroll(eventData);
    502.             else
    503.             {
    504.                 if (!IsActive())
    505.                     return;
    506.  
    507.                 EnsureLayoutHasRebuilt();
    508.                 UpdateBounds();
    509.  
    510.                 Vector2 delta = eventData.scrollDelta;
    511.                 // Down is positive for scroll events, while in UI system up is positive.
    512.                 delta.y *= -1;
    513.                 if (VerticallyScrollable && !HorizontallyScrollable)
    514.                 {
    515.                     if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
    516.                         delta.y = delta.x;
    517.                     delta.x = 0;
    518.                 }
    519.                 if (HorizontallyScrollable && !VerticallyScrollable)
    520.                 {
    521.                     if (Mathf.Abs(delta.y) > Mathf.Abs(delta.x))
    522.                         delta.x = delta.y;
    523.                     delta.y = 0;
    524.                 }
    525.  
    526.                 Vector2 position = content.anchoredPosition;
    527.                 position += delta * scrollSensitivity;
    528.                 if (scrollingMovementType == MovementType.Clamped)
    529.                     position += CalculateOffset(position - content.anchoredPosition);
    530.  
    531.                 SetContentAnchoredPosition(position);
    532.                 UpdateBounds();
    533.             }
    534.         }
    535.  
    536.         protected virtual void SetContentAnchoredPosition(Vector2 position)
    537.         {
    538.             if (!horizontallyScrollable)
    539.                 position.x = content.anchoredPosition.x;
    540.             if (!verticallyScrollable)
    541.                 position.y = content.anchoredPosition.y;
    542.  
    543.             if (position != content.anchoredPosition)
    544.             {
    545.                 content.anchoredPosition = position;
    546.                 UpdateBounds();
    547.             }
    548.         }
    549.  
    550.         protected virtual void LateUpdate()
    551.         {
    552.             if (!content)
    553.                 return;
    554.  
    555.             EnsureLayoutHasRebuilt();
    556.             UpdateScrollbarVisibility();
    557.             UpdateBounds();
    558.             float deltaTime = Time.unscaledDeltaTime;
    559.             Vector2 offset = CalculateOffset(Vector2.zero);
    560.             if (!dragging && (offset != Vector2.zero || velocity != Vector2.zero))
    561.             {
    562.                 Vector2 position = content.anchoredPosition;
    563.                 for (int axis = 0; axis < 2; axis++)
    564.                 {
    565.                     // Apply spring physics if movement is elastic and content has an offset from the view.
    566.                     if (scrollingMovementType == MovementType.Elastic && offset[axis] != 0)
    567.                     {
    568.                         float speed = velocity[axis];
    569.                         position[axis] = Mathf.SmoothDamp(content.anchoredPosition[axis], content.anchoredPosition[axis] + offset[axis], ref speed, elasticity, Mathf.Infinity, deltaTime);
    570.                         velocity[axis] = speed;
    571.                     }
    572.                     // Else move content according to velocity with deceleration applied.
    573.                     else if (inertia)
    574.                     {
    575.                         velocity[axis] *= Mathf.Pow(decelerationRate, deltaTime);
    576.                         if (Mathf.Abs(velocity[axis]) < 1)
    577.                             velocity[axis] = 0;
    578.                         position[axis] += velocity[axis] * deltaTime;
    579.                     }
    580.                     // If we have neither elaticity or friction, there shouldn't be any velocity.
    581.                     else
    582.                     {
    583.                         velocity[axis] = 0;
    584.                     }
    585.                 }
    586.  
    587.                 if (velocity != Vector2.zero)
    588.                 {
    589.                     if (scrollingMovementType == MovementType.Clamped)
    590.                     {
    591.                         offset = CalculateOffset(position - content.anchoredPosition);
    592.                         position += offset;
    593.                     }
    594.  
    595.                     SetContentAnchoredPosition(position);
    596.                 }
    597.             }
    598.  
    599.             if (dragging && inertia)
    600.             {
    601.                 Vector3 newVelocity = (content.anchoredPosition - prevPosition) / deltaTime;
    602.                 velocity = Vector3.Lerp(velocity, newVelocity, deltaTime * 10);
    603.             }
    604.  
    605.             if (viewBounds != prevViewBounds || contentBounds != prevContentBounds || content.anchoredPosition != prevPosition)
    606.             {
    607.                 UpdateScrollbars(offset);
    608.                 onValueChanged.Invoke(NormalizedPosition);
    609.                 UpdatePrevData();
    610.             }
    611.         }
    612.  
    613.         private void UpdatePrevData()
    614.         {
    615.             if (content == null)
    616.                 prevPosition = Vector2.zero;
    617.             else
    618.                 prevPosition = content.anchoredPosition;
    619.             prevViewBounds = viewBounds;
    620.             prevContentBounds = contentBounds;
    621.         }
    622.  
    623.         private void UpdateScrollbars(Vector2 offset)
    624.         {
    625.             if (horizontalScrollbar)
    626.             {
    627.                 if (contentBounds.size.x > 0)
    628.                     horizontalScrollbar.size = Mathf.Clamp01((viewBounds.size.x - Mathf.Abs(offset.x)) / contentBounds.size.x);
    629.                 else
    630.                     horizontalScrollbar.size = 1;
    631.  
    632.                 horizontalScrollbar.value = HorizontalNormalizedPosition;
    633.             }
    634.  
    635.             if (verticalScrollbar)
    636.             {
    637.                 if (contentBounds.size.y > 0)
    638.                     verticalScrollbar.size = Mathf.Clamp01((viewBounds.size.y - Mathf.Abs(offset.y)) / contentBounds.size.y);
    639.                 else
    640.                     verticalScrollbar.size = 1;
    641.  
    642.                 verticalScrollbar.value = VerticalNormalizedPosition;
    643.             }
    644.         }
    645.  
    646.         public Vector2 NormalizedPosition
    647.         {
    648.             get
    649.             {
    650.                 return new Vector2(HorizontalNormalizedPosition, VerticalNormalizedPosition);
    651.             }
    652.             set
    653.             {
    654.                 SetNormalizedPosition(value.x, 0);
    655.                 SetNormalizedPosition(value.y, 1);
    656.             }
    657.         }
    658.  
    659.         public float HorizontalNormalizedPosition
    660.         {
    661.             get
    662.             {
    663.                 UpdateBounds();
    664.                 if (contentBounds.size.x <= viewBounds.size.x)
    665.                     return (viewBounds.min.x > contentBounds.min.x) ? 1 : 0;
    666.                 return (viewBounds.min.x - contentBounds.min.x) / (contentBounds.size.x - viewBounds.size.x);
    667.             }
    668.             set
    669.             {
    670.                 SetNormalizedPosition(value, 0);
    671.             }
    672.         }
    673.  
    674.         public float VerticalNormalizedPosition
    675.         {
    676.             get
    677.             {
    678.                 UpdateBounds();
    679.                 if (contentBounds.size.y <= viewBounds.size.y)
    680.                     return (viewBounds.min.y > contentBounds.min.y) ? 1 : 0;
    681.                 ;
    682.                 return (viewBounds.min.y - contentBounds.min.y) / (contentBounds.size.y - viewBounds.size.y);
    683.             }
    684.             set
    685.             {
    686.                 SetNormalizedPosition(value, 1);
    687.             }
    688.         }
    689.  
    690.         private void SetHorizontalNormalizedPosition(float value) { SetNormalizedPosition(value, 0); }
    691.         private void SetVerticalNormalizedPosition(float value) { SetNormalizedPosition(value, 1); }
    692.  
    693.         private void SetNormalizedPosition(float value, int axis)
    694.         {
    695.             EnsureLayoutHasRebuilt();
    696.             UpdateBounds();
    697.             // How much the content is larger than the view.
    698.             float hiddenLength = contentBounds.size[axis] - viewBounds.size[axis];
    699.             // Where the position of the lower left corner of the content bounds should be, in the space of the view.
    700.             float contentBoundsMinPosition = viewBounds.min[axis] - value * hiddenLength;
    701.             // The new content localPosition, in the space of the view.
    702.             float newLocalPosition = content.localPosition[axis] + contentBoundsMinPosition - contentBounds.min[axis];
    703.  
    704.             Vector3 localPosition = content.localPosition;
    705.             if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f)
    706.             {
    707.                 localPosition[axis] = newLocalPosition;
    708.                 content.localPosition = localPosition;
    709.                 velocity[axis] = 0;
    710.                 UpdateBounds();
    711.             }
    712.         }
    713.  
    714.         private static float RubberDelta(float overStretching, float viewSize)
    715.         {
    716.             return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
    717.         }
    718.  
    719.         protected override void OnRectTransformDimensionsChange()
    720.         {
    721.             SetDirty();
    722.         }
    723.  
    724.         private bool HScrollingNeeded
    725.         {
    726.             get
    727.             {
    728.                 if (Application.isPlaying)
    729.                     return contentBounds.size.x > viewBounds.size.x + 0.01f;
    730.                 return true;
    731.             }
    732.         }
    733.         private bool VScrollingNeeded
    734.         {
    735.             get
    736.             {
    737.                 if (Application.isPlaying)
    738.                     return contentBounds.size.y > viewBounds.size.y + 0.01f;
    739.                 return true;
    740.             }
    741.         }
    742.  
    743.         public virtual void CalculateLayoutInputHorizontal() { }
    744.         public virtual void CalculateLayoutInputVertical() { }
    745.  
    746.         public virtual float minWidth { get { return -1; } }
    747.         public virtual float preferredWidth { get { return -1; } }
    748.         public virtual float flexibleWidth { get; private set; }
    749.  
    750.         public virtual float minHeight { get { return -1; } }
    751.         public virtual float preferredHeight { get { return -1; } }
    752.         public virtual float flexibleHeight { get { return -1; } }
    753.  
    754.         public virtual int layoutPriority { get { return -1; } }
    755.  
    756.         public virtual void SetLayoutHorizontal()
    757.         {
    758.             tracker.Clear();
    759.  
    760.             if (hSliderExpand || vSliderExpand)
    761.             {
    762.                 tracker.Add(this, ViewRect,
    763.                     DrivenTransformProperties.Anchors |
    764.                     DrivenTransformProperties.SizeDelta |
    765.                     DrivenTransformProperties.AnchoredPosition);
    766.  
    767.                 // Make view full size to see if content fits.
    768.                 ViewRect.anchorMin = Vector2.zero;
    769.                 ViewRect.anchorMax = Vector2.one;
    770.                 ViewRect.sizeDelta = Vector2.zero;
    771.                 ViewRect.anchoredPosition = Vector2.zero;
    772.              
    773.                 // Recalculate content layout with this size to see if it fits when there are no scrollbars.
    774.                 LayoutRebuilder.ForceRebuildLayoutImmediate(content);
    775.                 viewBounds = new Bounds(ViewRect.rect.center, ViewRect.rect.size);
    776.                 contentBounds = GetBounds();
    777.             }
    778.  
    779.             // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it.
    780.             if (vSliderExpand && VScrollingNeeded)
    781.             {
    782.                 ViewRect.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), ViewRect.sizeDelta.y);
    783.              
    784.                 // Recalculate content layout with this size to see if it fits vertically
    785.                 // when there is a vertical scrollbar (which may reflowed the content to make it taller).
    786.                 LayoutRebuilder.ForceRebuildLayoutImmediate(content);
    787.                 viewBounds = new Bounds(ViewRect.rect.center, ViewRect.rect.size);
    788.                 contentBounds = GetBounds();
    789.             }
    790.  
    791.             // If it doesn't fit horizontally, enable horizontal scrollbar and shrink view vertically to make room for it.
    792.             if (hSliderExpand && HScrollingNeeded)
    793.             {
    794.                 ViewRect.sizeDelta = new Vector2(ViewRect.sizeDelta.x, -(hSliderHeight + horizontalScrollbarSpacing));
    795.                 viewBounds = new Bounds(ViewRect.rect.center, ViewRect.rect.size);
    796.                 contentBounds = GetBounds();
    797.             }
    798.  
    799.             // If the vertical slider didn't kick in the first time, and the horizontal one did,
    800.             // we need to check again if the vertical slider now needs to kick in.
    801.             // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it.
    802.             if (vSliderExpand && VScrollingNeeded && ViewRect.sizeDelta.x == 0 && ViewRect.sizeDelta.y < 0)
    803.             {
    804.                 ViewRect.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), ViewRect.sizeDelta.y);
    805.             }
    806.         }
    807.  
    808.         public virtual void SetLayoutVertical()
    809.         {
    810.             UpdateScrollbarLayout();
    811.             viewBounds = new Bounds(ViewRect.rect.center, ViewRect.rect.size);
    812.             contentBounds = GetBounds();
    813.         }
    814.  
    815.         private void UpdateScrollbarVisibility()
    816.         {
    817.             if (verticalScrollbar && verticalScrollbarVisibility != ScrollbarVisibility.Permanent && verticalScrollbar.gameObject.activeSelf != VScrollingNeeded)
    818.                 verticalScrollbar.gameObject.SetActive(VScrollingNeeded);
    819.  
    820.             if (horizontalScrollbar && horizontalScrollbarVisibility != ScrollbarVisibility.Permanent && horizontalScrollbar.gameObject.activeSelf != HScrollingNeeded)
    821.                 horizontalScrollbar.gameObject.SetActive(HScrollingNeeded);
    822.         }
    823.  
    824.         private void UpdateScrollbarLayout()
    825.         {
    826.             if (vSliderExpand && horizontalScrollbar)
    827.             {
    828.                 tracker.Add(this, horizontalScrollbarRect,
    829.                     DrivenTransformProperties.AnchorMinX |
    830.                     DrivenTransformProperties.AnchorMaxX |
    831.                     DrivenTransformProperties.SizeDeltaX |
    832.                     DrivenTransformProperties.AnchoredPositionX);
    833.                 horizontalScrollbarRect.anchorMin = new Vector2(0, horizontalScrollbarRect.anchorMin.y);
    834.                 horizontalScrollbarRect.anchorMax = new Vector2(1, horizontalScrollbarRect.anchorMax.y);
    835.                 horizontalScrollbarRect.anchoredPosition = new Vector2(0, horizontalScrollbarRect.anchoredPosition.y);
    836.                 if (VScrollingNeeded)
    837.                     horizontalScrollbarRect.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), horizontalScrollbarRect.sizeDelta.y);
    838.                 else
    839.                     horizontalScrollbarRect.sizeDelta = new Vector2(0, horizontalScrollbarRect.sizeDelta.y);
    840.             }
    841.  
    842.             if (hSliderExpand && verticalScrollbar)
    843.             {
    844.                 tracker.Add(this, verticalScrollbarRect,
    845.                     DrivenTransformProperties.AnchorMinY |
    846.                     DrivenTransformProperties.AnchorMaxY |
    847.                     DrivenTransformProperties.SizeDeltaY |
    848.                     DrivenTransformProperties.AnchoredPositionY);
    849.                 verticalScrollbarRect.anchorMin = new Vector2(verticalScrollbarRect.anchorMin.x, 0);
    850.                 verticalScrollbarRect.anchorMax = new Vector2(verticalScrollbarRect.anchorMax.x, 1);
    851.                 verticalScrollbarRect.anchoredPosition = new Vector2(verticalScrollbarRect.anchoredPosition.x, 0);
    852.                 if (HScrollingNeeded)
    853.                     verticalScrollbarRect.sizeDelta = new Vector2(verticalScrollbarRect.sizeDelta.x, -(hSliderHeight + horizontalScrollbarSpacing));
    854.                 else
    855.                     verticalScrollbarRect.sizeDelta = new Vector2(verticalScrollbarRect.sizeDelta.x, 0);
    856.             }
    857.         }
    858.  
    859.         private void UpdateBounds()
    860.         {
    861.             viewBounds = new Bounds(ViewRect.rect.center, ViewRect.rect.size);
    862.             contentBounds = GetBounds();
    863.  
    864.             if (content == null)
    865.                 return;
    866.  
    867.             // Make sure content bounds are at least as large as view by adding padding if not.
    868.             // One might think at first that if the content is smaller than the view, scrolling should be allowed.
    869.             // However, that's not how scroll views normally work.
    870.             // Scrolling is *only* possible when content is *larger* than view.
    871.             // We use the pivot of the content rect to decide in which directions the content bounds should be expanded.
    872.             // E.g. if pivot is at top, bounds are expanded downwards.
    873.             // This also works nicely when ContentSizeFitter is used on the content.
    874.             Vector3 contentSize = contentBounds.size;
    875.             Vector3 contentPos = contentBounds.center;
    876.             Vector3 excess = viewBounds.size - contentSize;
    877.             if (excess.x > 0)
    878.             {
    879.                 contentPos.x -= excess.x * (content.pivot.x - 0.5f);
    880.                 contentSize.x = viewBounds.size.x;
    881.             }
    882.             if (excess.y > 0)
    883.             {
    884.                 contentPos.y -= excess.y * (content.pivot.y - 0.5f);
    885.                 contentSize.y = viewBounds.size.y;
    886.             }
    887.  
    888.             contentBounds.size = contentSize;
    889.             contentBounds.center = contentPos;
    890.         }
    891.  
    892.         private readonly Vector3[] m_Corners = new Vector3[4];
    893.         private Bounds GetBounds()
    894.         {
    895.             if (content == null)
    896.                 return new Bounds();
    897.  
    898.             var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
    899.             var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
    900.  
    901.             var toLocal = ViewRect.worldToLocalMatrix;
    902.             content.GetWorldCorners(m_Corners);
    903.             for (int j = 0; j < 4; j++)
    904.             {
    905.                 Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
    906.                 vMin = Vector3.Min(v, vMin);
    907.                 vMax = Vector3.Max(v, vMax);
    908.             }
    909.  
    910.             var bounds = new Bounds(vMin, Vector3.zero);
    911.             bounds.Encapsulate(vMax);
    912.             return bounds;
    913.         }
    914.  
    915.         private Vector2 CalculateOffset(Vector2 delta)
    916.         {
    917.             Vector2 offset = Vector2.zero;
    918.             if (scrollingMovementType == MovementType.Unrestricted)
    919.                 return offset;
    920.  
    921.             Vector2 min = contentBounds.min;
    922.             Vector2 max = contentBounds.max;
    923.  
    924.             if (horizontallyScrollable)
    925.             {
    926.                 min.x += delta.x;
    927.                 max.x += delta.x;
    928.                 if (min.x > viewBounds.min.x)
    929.                     offset.x = viewBounds.min.x - min.x;
    930.                 else if (max.x < viewBounds.max.x)
    931.                     offset.x = viewBounds.max.x - max.x;
    932.             }
    933.  
    934.             if (verticallyScrollable)
    935.             {
    936.                 min.y += delta.y;
    937.                 max.y += delta.y;
    938.                 if (max.y < viewBounds.max.y)
    939.                     offset.y = viewBounds.max.y - max.y;
    940.                 else if (min.y > viewBounds.min.y)
    941.                     offset.y = viewBounds.min.y - min.y;
    942.             }
    943.  
    944.             return offset;
    945.         }
    946.  
    947.         protected void SetDirty()
    948.         {
    949.             if (!IsActive())
    950.                 return;
    951.  
    952.             LayoutRebuilder.MarkLayoutForRebuild(RectTransform);
    953.         }
    954.  
    955.         protected void SetDirtyCaching()
    956.         {
    957.             if (!IsActive())
    958.                 return;
    959.  
    960.             CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
    961.             LayoutRebuilder.MarkLayoutForRebuild(RectTransform);
    962.         }
    963.  
    964. #if UNITY_EDITOR
    965.         protected override void OnValidate()
    966.         {
    967.             SetDirtyCaching();
    968.         }
    969. #endif
    970.     }
    971. }
    BaseScrollRectEditor.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEditor.AnimatedValues;
    4. using UnityEditor;
    5.  
    6. namespace TheRamanEffect.Unity.UI.Editors
    7. {
    8.     [CustomEditor(typeof(BaseScrollRect), true)]
    9.     [CanEditMultipleObjects]
    10.     public class BaseScrollRectEditor : Editor
    11.     {
    12.         SerializedProperty content;
    13.         SerializedProperty horizontallyScrollable;
    14.         SerializedProperty verticallyScrollable;
    15.         SerializedProperty scrollingMovementType;
    16.         SerializedProperty elasticity;
    17.         SerializedProperty inertia;
    18.         SerializedProperty decelerationRate;
    19.         SerializedProperty scrollSensitivity;
    20.         SerializedProperty viewportRectTransform;
    21.         SerializedProperty horizontalScrollbar;
    22.         SerializedProperty verticalScrollbar;
    23.         SerializedProperty horizontalScrollbarVisibility;
    24.         SerializedProperty verticalScrollbarVisibility;
    25.         SerializedProperty horizontalScrollbarSpacing;
    26.         SerializedProperty verticalScrollbarSpacing;
    27.         SerializedProperty forwardUnusedEventsToContainer;
    28.         SerializedProperty onValueChanged;
    29.         AnimBool showElasticity;
    30.         AnimBool showDecelerationRate;
    31.         bool viewportIsNotChild, hScrollbarIsNotChild, vScrollbarIsNotChild;
    32.         static string s_HError = "For this visibility mode, the Viewport property and the Horizontal Scrollbar property both needs to be set to a Rect Transform that is a child to the Scroll Rect.";
    33.         static string s_VError = "For this visibility mode, the Viewport property and the Vertical Scrollbar property both needs to be set to a Rect Transform that is a child to the Scroll Rect.";
    34.  
    35.         protected virtual void OnEnable()
    36.         {
    37.             content = serializedObject.FindProperty("content");
    38.             horizontallyScrollable = serializedObject.FindProperty("horizontallyScrollable");
    39.             verticallyScrollable = serializedObject.FindProperty("verticallyScrollable");
    40.             scrollingMovementType = serializedObject.FindProperty("scrollingMovementType");
    41.             elasticity = serializedObject.FindProperty("elasticity");
    42.             inertia = serializedObject.FindProperty("inertia");
    43.             decelerationRate = serializedObject.FindProperty("decelerationRate");
    44.             scrollSensitivity = serializedObject.FindProperty("scrollSensitivity");
    45.             viewportRectTransform = serializedObject.FindProperty("viewportRectTransform");
    46.             horizontalScrollbar = serializedObject.FindProperty("horizontalScrollbar");
    47.             verticalScrollbar = serializedObject.FindProperty("verticalScrollbar");
    48.             horizontalScrollbarVisibility = serializedObject.FindProperty("horizontalScrollbarVisibility");
    49.             verticalScrollbarVisibility = serializedObject.FindProperty("verticalScrollbarVisibility");
    50.             horizontalScrollbarSpacing = serializedObject.FindProperty("horizontalScrollbarSpacing");
    51.             verticalScrollbarSpacing = serializedObject.FindProperty("verticalScrollbarSpacing");
    52.             forwardUnusedEventsToContainer = serializedObject.FindProperty("forwardUnusedEventsToContainer");
    53.             onValueChanged = serializedObject.FindProperty("onValueChanged");
    54.  
    55.             showElasticity = new AnimBool(Repaint);
    56.             showDecelerationRate = new AnimBool(Repaint);
    57.             SetAnimBools(true);
    58.         }
    59.  
    60.         protected virtual void OnDisable()
    61.         {
    62.             showElasticity.valueChanged.RemoveListener(Repaint);
    63.             showDecelerationRate.valueChanged.RemoveListener(Repaint);
    64.         }
    65.  
    66.         void SetAnimBools(bool instant)
    67.         {
    68.             SetAnimBool(showElasticity, !scrollingMovementType.hasMultipleDifferentValues && scrollingMovementType.enumValueIndex == (int)ScrollRect.MovementType.Elastic, instant);
    69.             SetAnimBool(showDecelerationRate, !inertia.hasMultipleDifferentValues && inertia.boolValue == true, instant);
    70.         }
    71.  
    72.         void SetAnimBool(AnimBool a, bool value, bool instant)
    73.         {
    74.             if (instant)
    75.                 a.value = value;
    76.             else
    77.                 a.target = value;
    78.         }
    79.  
    80.         void CalculateCachedValues()
    81.         {
    82.             viewportIsNotChild = false;
    83.             hScrollbarIsNotChild = false;
    84.             vScrollbarIsNotChild = false;
    85.             if (targets.Length == 1)
    86.             {
    87.                 Transform transform = ((BaseScrollRect)target).transform;
    88.                 if (viewportRectTransform.objectReferenceValue == null || ((RectTransform)viewportRectTransform.objectReferenceValue).transform.parent != transform)
    89.                     viewportIsNotChild = true;
    90.                 if (horizontalScrollbar.objectReferenceValue == null || ((Scrollbar)horizontalScrollbar.objectReferenceValue).transform.parent != transform)
    91.                     hScrollbarIsNotChild = true;
    92.                 if (verticalScrollbar.objectReferenceValue == null || ((Scrollbar)verticalScrollbar.objectReferenceValue).transform.parent != transform)
    93.                     vScrollbarIsNotChild = true;
    94.             }
    95.         }
    96.  
    97.         public override void OnInspectorGUI()
    98.         {
    99.             SetAnimBools(false);
    100.  
    101.             serializedObject.Update();
    102.             // Once we have a reliable way to know if the object changed, only re-cache in that case.
    103.             CalculateCachedValues();
    104.  
    105.             EditorGUILayout.PropertyField(content);
    106.  
    107.             EditorGUILayout.PropertyField(horizontallyScrollable);
    108.             EditorGUILayout.PropertyField(verticallyScrollable);
    109.  
    110.             EditorGUILayout.PropertyField(scrollingMovementType);
    111.             if (EditorGUILayout.BeginFadeGroup(showElasticity.faded))
    112.             {
    113.                 EditorGUI.indentLevel++;
    114.                 EditorGUILayout.PropertyField(elasticity);
    115.                 EditorGUI.indentLevel--;
    116.             }
    117.             EditorGUILayout.EndFadeGroup();
    118.  
    119.             EditorGUILayout.PropertyField(inertia);
    120.             if (EditorGUILayout.BeginFadeGroup(showDecelerationRate.faded))
    121.             {
    122.                 EditorGUI.indentLevel++;
    123.                 EditorGUILayout.PropertyField(decelerationRate);
    124.                 EditorGUI.indentLevel--;
    125.             }
    126.             EditorGUILayout.EndFadeGroup();
    127.  
    128.             EditorGUILayout.PropertyField(scrollSensitivity);
    129.  
    130.             EditorGUILayout.Space();
    131.  
    132.             EditorGUILayout.PropertyField(viewportRectTransform);
    133.  
    134.             EditorGUILayout.PropertyField(horizontalScrollbar);
    135.             if (horizontalScrollbar.objectReferenceValue && !horizontalScrollbar.hasMultipleDifferentValues)
    136.             {
    137.                 EditorGUI.indentLevel++;
    138.                 EditorGUILayout.PropertyField(horizontalScrollbarVisibility, new GUIContent("Visibility"));
    139.  
    140.                 if ((ScrollRect.ScrollbarVisibility)horizontalScrollbarVisibility.enumValueIndex == ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport
    141.                     && !horizontalScrollbarVisibility.hasMultipleDifferentValues)
    142.                 {
    143.                     if (viewportIsNotChild || hScrollbarIsNotChild)
    144.                         EditorGUILayout.HelpBox(s_HError, MessageType.Error);
    145.                     EditorGUILayout.PropertyField(horizontalScrollbarSpacing, new GUIContent("Spacing"));
    146.                 }
    147.  
    148.                 EditorGUI.indentLevel--;
    149.             }
    150.  
    151.             EditorGUILayout.PropertyField(verticalScrollbar);
    152.             if (verticalScrollbar.objectReferenceValue && !verticalScrollbar.hasMultipleDifferentValues)
    153.             {
    154.                 EditorGUI.indentLevel++;
    155.                 EditorGUILayout.PropertyField(verticalScrollbarVisibility, new GUIContent("Visibility"));
    156.  
    157.                 if ((ScrollRect.ScrollbarVisibility)verticalScrollbarVisibility.enumValueIndex == ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport
    158.                     && !verticalScrollbarVisibility.hasMultipleDifferentValues)
    159.                 {
    160.                     if (viewportIsNotChild || vScrollbarIsNotChild)
    161.                         EditorGUILayout.HelpBox(s_VError, MessageType.Error);
    162.                     EditorGUILayout.PropertyField(verticalScrollbarSpacing, new GUIContent("Spacing"));
    163.                 }
    164.  
    165.                 EditorGUI.indentLevel--;
    166.             }
    167.  
    168.             EditorGUILayout.Space();
    169.  
    170.             EditorGUILayout.PropertyField(forwardUnusedEventsToContainer);
    171.  
    172.             EditorGUILayout.Space();
    173.  
    174.             EditorGUILayout.PropertyField(onValueChanged);
    175.  
    176.             serializedObject.ApplyModifiedProperties();
    177.         }
    178.     }
    179. }
    I'm now using this class in place of any and all ScrollRects in my projects. I hope you find it useful too!

    Cheers!

    PS: Please be aware of the capitalizations in all properties (I've changed up the code to follow my naming conventions)
     

    Attached Files:

    Last edited: Mar 22, 2018
    Vu-Quang, killer_mech, Atrixx and 2 others like this.
  47. mikegogogo

    mikegogogo

    Joined:
    May 10, 2018
    Posts:
    2
  48. ArjunKPrahaladan

    ArjunKPrahaladan

    Joined:
    May 17, 2017
    Posts:
    13
    I cant seem to figure out how it works..
    I am also in a situation but cant figure out how to implement this.
    Can someone walk me through on what to do with the class provided
     
    Last edited: May 23, 2018
  49. ferretnt

    ferretnt

    Joined:
    Apr 10, 2012
    Posts:
    309
    @CaptainSchnittchen I'm not known for my love of Unity UI or the posts about it, which generally strike me as an abandoned, unsupported, semi-source-code blob through which the blind lead the blind, but your post is outstanding. Well done sir!
     
  50. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    187
    Wow!!! Thank you very much, @CaptainSchnittchen