Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Automatically send events to sibling components and parents (UI)

Discussion in 'Scripting' started by IgorAherne, Apr 16, 2017.

  1. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    383
    Hey people :)
    I've made a script which allows to automatically send the copy of received event to the UI parents / grandparent gameObjects

    It's a simple monobehavior and will always automatically place itself on top of all attached components! :D

    Imagine having a parent UI panel play a sound while you are touching an icon that is a child of such a panel, Both, Panel, Icon will be able to process event simultaneously

    This will work even with things like default Image / TextMeshPro / scrollRect, etc - out of the box

    I hope you find it useful, have a good day
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditorInternal;
    4. using UnityEngine.EventSystems;
    5.  
    6. // Igor Aherne 4/16/2017
    7. // https://www.facebook.com/igor.aherne
    8. //
    9. // component which always stays on top of the gameObject.
    10. // This means it will ALWAYS be first to receive UI events.
    11. // you can make it to be at the bottom instead, by unticking "_keep_component_on_top"
    12. // In that case, parents will be notified AFTER all the sibling components have
    13. // processed the event
    14. //
    15. // NOTICE, if the raycast arrived to this gameObject from a simple child-image, etc,
    16. // Then ALL of our siblings components on this gameObject will receive the events, not
    17. // just this component. In such a case it won't matter if this instance has
    18. // "_keep_component_on_top == true"
    19. //
    20. // But for further propagation (to our parents) the placement of THEIR  'UI_eventSurviveForParent'
    21. // within their component stacks will matter, and will determine if their other components (placed under)
    22. // will be getting events or not.
    23. //
    24. // This component ensures required events will reach the first component
    25. // who is capable of executing such an event, on one of the
    26. // parents (parent --> grandParent --> etc)
    27. [ExecuteInEditMode]
    28. public class UI_eventSurviveForParent : MonoBehaviour, IPointerDownHandler,
    29.                                                        IPointerUpHandler,
    30.                                                        IDragHandler,
    31.                                                        IBeginDragHandler,
    32.                                                        IEndDragHandler {
    33. #region vars
    34.     public bool _propagate_Drag = true;
    35.     public bool _propagate_BeginDrag = true;
    36.     public bool _propagate_EndDrag = true;
    37.  
    38.     [Space(5)]
    39.     public bool _propagate_PointerDown = true;
    40.     public bool _propagate_PointerUp = true;
    41.  
    42.     [Space(5)]
    43.     public bool _keep_component_on_top = true;
    44. #endregion
    45.  
    46.  
    47.  
    48. #if UNITY_EDITOR
    49.     void Update() {
    50.         if (_keep_component_on_top) {
    51.             //make sure this script will always be on top of all components.
    52.             while (ComponentUtility.MoveComponentUp(this)) {
    53.                 continue;
    54.             }//end while
    55.             return;
    56.         }
    57.         //else, keep on bottom
    58.         while (ComponentUtility.MoveComponentDown(this)) {
    59.             continue;
    60.         }//end while
    61.     }
    62. #endif
    63.  
    64.  
    65.     #region handlers
    66.     public void OnBeginDrag(PointerEventData eventData) {
    67.         if (_propagate_BeginDrag == false) {
    68.             return;
    69.         }
    70.         PropagateEvent<IBeginDragHandler>(eventData,
    71.                                            ExecuteEvents.beginDragHandler);
    72.     }
    73.  
    74.  
    75.     public void OnDrag(PointerEventData eventData) {
    76.         if (_propagate_Drag == false) {
    77.             return;
    78.         }
    79.         PropagateEvent<IDragHandler>(eventData,
    80.                                       ExecuteEvents.dragHandler);
    81.     }
    82.  
    83.  
    84.     public void OnEndDrag(PointerEventData eventData) {
    85.         if (_propagate_EndDrag == false) {
    86.             return;
    87.         }
    88.         PropagateEvent<IEndDragHandler>(eventData,
    89.                                         ExecuteEvents.endDragHandler);
    90.     }
    91.  
    92.  
    93.  
    94.     public void OnPointerDown(PointerEventData eventData) {
    95.         if (_propagate_PointerDown == false) {
    96.             return;
    97.         }
    98.         PropagateEvent<IPointerDownHandler>(eventData,
    99.                                              ExecuteEvents.pointerDownHandler);
    100.     }
    101.  
    102.  
    103.     public void OnPointerUp(PointerEventData eventData) {
    104.         if (_propagate_PointerUp == false) {
    105.             return;
    106.         }
    107.         PropagateEvent<IPointerUpHandler>(eventData,
    108.                                            ExecuteEvents.pointerUpHandler);
    109.     }
    110.     #endregion
    111.  
    112.  
    113.     private void PropagateEvent<T>(PointerEventData eventData,
    114.                                     ExecuteEvents.EventFunction<T> handler_method) where T : IEventSystemHandler {
    115.  
    116.         //invoke on parents untill one is executed:
    117.         ExecuteEvents.ExecuteHierarchy(transform.parent.gameObject,
    118.                                         eventData,
    119.                                         handler_method);
    120.  
    121.         eventData.Reset();
    122.     }
    123.  
    124.  
    125. }
    126.  
    127.  
     
    Last edited: Sep 18, 2017
  2. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    383
    Version 2.0 with couple of observations, + a black list for ignoring event-propagation to unwanted components.
    Notice, blacklist won't have effect for local components, they would still end up receiving the event.

    There is also an whitelist-array which can be populated from the inspector for particular instance of our component. Read more in comments

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using System;
    5. using System.Linq;
    6.  
    7. #if UNITY_EDITOR
    8. using UnityEditorInternal;
    9. #endif
    10.  
    11. // Igor Aherne 9/May/2017
    12. // https://www.facebook.com/igor.aherne
    13. //
    14. // component which always stays on top of the gameObject.
    15. // This means it will ALWAYS be first to receive UI events.
    16. // you can make it to be at the bottom instead, by unticking "_keep_component_on_top"
    17. // In that case, parents will be the last to handle the incoming event,
    18. // AFTER all the sibling components have processed this event.
    19. //
    20. // NOTICE - All local components process an incomming event.
    21. // In other words, black-listing won't have effect for local components, they would
    22. // still end up receiving the event.
    23. //
    24. // Surviving non-handled portion of the event gets sent to parent gameObjects.
    25. //
    26. // This component ensures required events will reach the first component
    27. // who is capable of executing such an event, on one of the
    28. // parents (parent --> grandParent --> etc)
    29. [ExecuteInEditMode]
    30. public class UI_eventSurviveForParent : MonoBehaviour, IPointerDownHandler,
    31.                                                        IPointerUpHandler,
    32.                                                        IDragHandler,
    33.                                                        IBeginDragHandler,
    34.                                                        IEndDragHandler {
    35.     #region vars
    36.     public bool _propagate_Drag = true;
    37.     public bool _propagate_BeginDrag = true;
    38.     public bool _propagate_EndDrag = true;
    39.  
    40.     [Space(5)]
    41.     public bool _propagate_PointerDown = true;
    42.     public bool _propagate_PointerUp = true;
    43.  
    44.     [Space(5)]
    45.     public bool _keep_component_on_top = true;
    46.  
    47.     //tell which components you wish to ignore in parents.
    48.     //Such components would typically communicate to each other on their own.
    49.     //By ignoring them we ensure they won't receive double-events.
    50.     private static Type[] _blacklistedTypes_IN_PARENTS = new Type[] { typeof(ScrollRectEx) };
    51.  
    52.  
    53.     // blacklisted type  <  whitelist GO
    54.     //
    55.     // Supply object to still be served with our event, even if its components are blacklisted.
    56.     // BE VERY CAREFUL when supplying such gameObjects. You might accidentally forward an event
    57.     // a second time to a component which has already received an event from one of its instances,
    58.     // from the children GO (if we whitelist ScrollRectEX, that talk to each over by default)
    59.     //
    60.     // However, this list is very useful - assume we blacklist "ScrollRectEx.cs":
    61.     //
    62.     // --ScrollRectEX1                       (would forward event to parent ScrollRectEX)
    63.     // -----ScrollRectEX2                    (would forward event to parent ScrollRectEX)
    64.     // ----------UI_eventSurviveForParent1   (passes event up but ignores any ScrollRectEx)
    65.     // ------------UI_eventSurviveForParent2 (passes event up but ignores any ScrollRectEx)
    66.     // --------------RaycastImageGraphic     (lowest child, receives & propagates the event up)
    67.     //
    68.     // Notice, when image receives input, the lowest scrollRectEX won't get any event,
    69.     // since UI_eventSurviveForPanent blacklists it usually.
    70.     // For everything to work in this example, make sure to whitelist  ScrollRectEX2  in  UI_eventSurviveForParent1
    71.     //
    72.     // However, if UI_eventSurviveForParent would be located on same gameObject as ScrollRectEX, we don't need to
    73.     // worry about this, BECAUSE ALL LOCAL components receive event once it reaches a gameObject.
    74.     [SerializeField, Space(15)]
    75.     public GameObject[] _whitelisted_gos;
    76.  
    77.     // whitelist  <  blacklisted component instance
    78.     //
    79.     // We will not consider a component, even if its gameObject was whitelisted.
    80.     [SerializeField]
    81.     public Component[] _blacklisted_Components_IN_PARENTS;
    82.     #endregion
    83.  
    84.  
    85.    
    86. #if UNITY_EDITOR
    87.     void Update() {
    88.         //This will allow our editor-hierarchy-icons scripts to put an icon into the hierarchy.
    89.         //With its help we can see which objsect have this script, and which ones are intended to be blocking
    90.         if (isBlockingAll()) {
    91.             gameObject.tag = "UI_eventSurviveForParent_blocking";
    92.         }
    93.  
    94.         //unless the user explicitly specified the tag to be "blocking" or "custom", enfore our own tag:
    95.         if(gameObject.tag != "UI_eventSurviveForParent_blocking"  &&  gameObject.tag != "UI_eventSurviveForParent_custom") {
    96.             gameObject.tag = "UI_eventSurviveForParent";
    97.         }
    98.  
    99.         if (_keep_component_on_top) {
    100.             //make sure this script will always be on top of all components.
    101.             while (ComponentUtility.MoveComponentUp(this)) {
    102.                 continue;
    103.             }//end while
    104.             return;
    105.         }
    106.         //else, keep on bottom
    107.         while (ComponentUtility.MoveComponentDown(this)) {
    108.             continue;
    109.         }//end while
    110.     }
    111.  
    112.  
    113.     void OnDestroy() {
    114.         gameObject.tag = "Untagged";
    115.     }
    116. #endif
    117.  
    118.  
    119.     #region handlers
    120.     public void OnBeginDrag(PointerEventData eventData) {
    121.         if (_propagate_BeginDrag == false) {
    122.             return;
    123.         }
    124.  
    125.         Handle_onSomeParent<IInitializePotentialDragHandler>((parent) => { parent.OnInitializePotentialDrag(eventData); });
    126.         Handle_onSomeParent<IBeginDragHandler>((parent) => { parent.OnBeginDrag(eventData); });
    127.     }
    128.  
    129.  
    130.     public void OnDrag(PointerEventData eventData) {
    131.         if (_propagate_Drag == false) {
    132.             return;
    133.         }
    134.        
    135.         Handle_onSomeParent<IDragHandler>((parent) => { parent.OnDrag(eventData); });
    136.     }
    137.  
    138.  
    139.  
    140.     public void OnEndDrag(PointerEventData eventData) {
    141.         if (_propagate_EndDrag == false) {
    142.             return;
    143.         }
    144.  
    145.         Handle_onSomeParent<IEndDragHandler>((parent) => { parent.OnEndDrag(eventData); });
    146.     }
    147.  
    148.  
    149.  
    150.     public void OnPointerDown(PointerEventData eventData) {
    151.         if (_propagate_PointerDown == false) {
    152.             return;
    153.         }
    154.  
    155.         Handle_onSomeParent<IPointerDownHandler>((parent) => { parent.OnPointerDown(eventData); });
    156.     }
    157.  
    158.  
    159.  
    160.  
    161.     public void OnPointerUp(PointerEventData eventData) {
    162.         if (_propagate_PointerUp == false) {
    163.             return;
    164.         }
    165.  
    166.         Handle_onSomeParent<IPointerUpHandler>((parent) => { parent.OnPointerUp(eventData); });
    167.     }
    168.     #endregion
    169.  
    170.  
    171.  
    172.  
    173.  
    174.     private void Handle_onSomeParent<T>(Action<T> action) where T : IEventSystemHandler {
    175.  
    176.         Transform parent = transform.parent;
    177.         bool parentHandled = false;
    178.  
    179.         while (parent != null) {
    180.             foreach (var component in parent.GetComponents<Component>()) {
    181.  
    182.                 #region check if skip
    183.                 //if component is not T (T is interface).
    184.                 //For some reason  isAssignableFrom(typeof(T))  didn't work
    185.                 if ( component.GetType().GetInterface(typeof(T).ToString()) == null ) {
    186.                     continue;
    187.                 }
    188.  
    189.                 //skip if this component instance was mentioned in our "blacklisted component instances"
    190.                 if(_blacklisted_Components_IN_PARENTS.Any(c=>c == component)) {
    191.                     continue;
    192.                 }
    193.  
    194.                 //if the gameObject is not whitelisted, and the component is blacklisted:
    195.                 if( _whitelisted_gos.Any(whiteGO => whiteGO==component.gameObject) == false
    196.                     && _blacklistedTypes_IN_PARENTS.Any(black=> black == component.GetType())  ) {
    197.                     continue; //skip such a black-listed component
    198.                 }
    199.                 #endregion
    200.              
    201.                 action((T)(IEventSystemHandler)component);
    202.                 parentHandled = true;
    203.             }//end foreach component
    204.  
    205.             if (parentHandled) {
    206.                 //all components on the handle-able parent were fed:
    207.                 return;
    208.             }
    209.  
    210.             //else, keep looking for parent who can handle such an event:
    211.             parent = parent.parent;
    212.         }//end while(parent != null)
    213.     }
    214.  
    215.  
    216.  
    217.  
    218.  
    219.     bool isBlockingAll() {
    220.  
    221.          if(_propagate_Drag) {
    222.             return false;
    223.          }
    224.          if(_propagate_BeginDrag) {
    225.             return false;
    226.         }
    227.         if (_propagate_EndDrag) {
    228.             return false;
    229.         }
    230.  
    231.         if (_propagate_PointerDown) {
    232.             return false;
    233.         }
    234.         if (_propagate_PointerUp) {
    235.             return false;
    236.         }
    237.  
    238.         return true;
    239.     }
    240.  
    241.  
    242.  
    243. }
    244.  
     
    Last edited: Oct 10, 2017
    dyupa likes this.