Automatically send events to sibling components and parents (UI)

Hey people :slight_smile:
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! :smile:

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

using UnityEngine;
using UnityEditorInternal;
using UnityEngine.EventSystems;

// Igor Aherne 4/16/2017
// https://www.facebook.com/igor.aherne
//
// component which always stays on top of the gameObject.
// This means it will ALWAYS be first to receive UI events.
// you can make it to be at the bottom instead, by unticking "_keep_component_on_top"
// In that case, parents will be notified AFTER all the sibling components have
// processed the event
//
// NOTICE, if the raycast arrived to this gameObject from a simple child-image, etc,
// Then ALL of our siblings components on this gameObject will receive the events, not
// just this component. In such a case it won't matter if this instance has
// "_keep_component_on_top == true"
//
// But for further propagation (to our parents) the placement of THEIR  'UI_eventSurviveForParent'
// within their component stacks will matter, and will determine if their other components (placed under)
// will be getting events or not.
//
// This component ensures required events will reach the first component
// who is capable of executing such an event, on one of the
// parents (parent --> grandParent --> etc)
[ExecuteInEditMode]
public class UI_eventSurviveForParent : MonoBehaviour, IPointerDownHandler,
                                                       IPointerUpHandler,
                                                       IDragHandler,
                                                       IBeginDragHandler,
                                                       IEndDragHandler {
#region vars
    public bool _propagate_Drag = true;
    public bool _propagate_BeginDrag = true;
    public bool _propagate_EndDrag = true;

    [Space(5)]
    public bool _propagate_PointerDown = true;
    public bool _propagate_PointerUp = true;

    [Space(5)]
    public bool _keep_component_on_top = true;
#endregion



#if UNITY_EDITOR
    void Update() {
        if (_keep_component_on_top) {
            //make sure this script will always be on top of all components.
            while (ComponentUtility.MoveComponentUp(this)) {
                continue;
            }//end while
            return;
        }
        //else, keep on bottom
        while (ComponentUtility.MoveComponentDown(this)) {
            continue;
        }//end while
    }
#endif


    #region handlers
    public void OnBeginDrag(PointerEventData eventData) {
        if (_propagate_BeginDrag == false) {
            return;
        }
        PropagateEvent<IBeginDragHandler>(eventData,
                                           ExecuteEvents.beginDragHandler);
    }


    public void OnDrag(PointerEventData eventData) {
        if (_propagate_Drag == false) {
            return;
        }
        PropagateEvent<IDragHandler>(eventData,
                                      ExecuteEvents.dragHandler);
    }


    public void OnEndDrag(PointerEventData eventData) {
        if (_propagate_EndDrag == false) {
            return;
        }
        PropagateEvent<IEndDragHandler>(eventData,
                                        ExecuteEvents.endDragHandler);
    }



    public void OnPointerDown(PointerEventData eventData) {
        if (_propagate_PointerDown == false) {
            return;
        }
        PropagateEvent<IPointerDownHandler>(eventData,
                                             ExecuteEvents.pointerDownHandler);
    }


    public void OnPointerUp(PointerEventData eventData) {
        if (_propagate_PointerUp == false) {
            return;
        }
        PropagateEvent<IPointerUpHandler>(eventData,
                                           ExecuteEvents.pointerUpHandler);
    }
    #endregion


    private void PropagateEvent<T>(PointerEventData eventData,
                                    ExecuteEvents.EventFunction<T> handler_method) where T : IEventSystemHandler {

        //invoke on parents untill one is executed:
        ExecuteEvents.ExecuteHierarchy(transform.parent.gameObject,
                                        eventData,
                                        handler_method);

        eventData.Reset();
    }


}

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

using UnityEngine;
using UnityEngine.EventSystems;
using System;
using System.Linq;

#if UNITY_EDITOR
using UnityEditorInternal;
#endif

// Igor Aherne 9/May/2017
// https://www.facebook.com/igor.aherne
//
// component which always stays on top of the gameObject.
// This means it will ALWAYS be first to receive UI events.
// you can make it to be at the bottom instead, by unticking "_keep_component_on_top"
// In that case, parents will be the last to handle the incoming event,
// AFTER all the sibling components have processed this event.
//
// NOTICE - All local components process an incomming event.
// In other words, black-listing won't have effect for local components, they would
// still end up receiving the event.
//
// Surviving non-handled portion of the event gets sent to parent gameObjects.
//
// This component ensures required events will reach the first component
// who is capable of executing such an event, on one of the
// parents (parent --> grandParent --> etc)
[ExecuteInEditMode]
public class UI_eventSurviveForParent : MonoBehaviour, IPointerDownHandler,
                                                       IPointerUpHandler,
                                                       IDragHandler,
                                                       IBeginDragHandler,
                                                       IEndDragHandler {
    #region vars
    public bool _propagate_Drag = true;
    public bool _propagate_BeginDrag = true;
    public bool _propagate_EndDrag = true;

    [Space(5)]
    public bool _propagate_PointerDown = true;
    public bool _propagate_PointerUp = true;

    [Space(5)]
    public bool _keep_component_on_top = true;

    //tell which components you wish to ignore in parents.
    //Such components would typically communicate to each other on their own.
    //By ignoring them we ensure they won't receive double-events.
    private static Type[] _blacklistedTypes_IN_PARENTS = new Type[] { typeof(ScrollRectEx) };


    // blacklisted type  <  whitelist GO
    //
    // Supply object to still be served with our event, even if its components are blacklisted.
    // BE VERY CAREFUL when supplying such gameObjects. You might accidentally forward an event
    // a second time to a component which has already received an event from one of its instances,
    // from the children GO (if we whitelist ScrollRectEX, that talk to each over by default)
    //
    // However, this list is very useful - assume we blacklist "ScrollRectEx.cs":
    //
    // --ScrollRectEX1                       (would forward event to parent ScrollRectEX)
    // -----ScrollRectEX2                    (would forward event to parent ScrollRectEX)
    // ----------UI_eventSurviveForParent1   (passes event up but ignores any ScrollRectEx)
    // ------------UI_eventSurviveForParent2 (passes event up but ignores any ScrollRectEx)
    // --------------RaycastImageGraphic     (lowest child, receives & propagates the event up)
    //
    // Notice, when image receives input, the lowest scrollRectEX won't get any event,
    // since UI_eventSurviveForPanent blacklists it usually.
    // For everything to work in this example, make sure to whitelist  ScrollRectEX2  in  UI_eventSurviveForParent1
    //
    // However, if UI_eventSurviveForParent would be located on same gameObject as ScrollRectEX, we don't need to
    // worry about this, BECAUSE ALL LOCAL components receive event once it reaches a gameObject.
    [SerializeField, Space(15)]
    public GameObject[] _whitelisted_gos;

    // whitelist  <  blacklisted component instance
    //
    // We will not consider a component, even if its gameObject was whitelisted.
    [SerializeField]
    public Component[] _blacklisted_Components_IN_PARENTS;
    #endregion


    
#if UNITY_EDITOR
    void Update() {
        //This will allow our editor-hierarchy-icons scripts to put an icon into the hierarchy.
        //With its help we can see which objsect have this script, and which ones are intended to be blocking
        if (isBlockingAll()) {
            gameObject.tag = "UI_eventSurviveForParent_blocking";
        }

        //unless the user explicitly specified the tag to be "blocking" or "custom", enfore our own tag:
        if(gameObject.tag != "UI_eventSurviveForParent_blocking"  &&  gameObject.tag != "UI_eventSurviveForParent_custom") {
            gameObject.tag = "UI_eventSurviveForParent";
        }

        if (_keep_component_on_top) {
            //make sure this script will always be on top of all components.
            while (ComponentUtility.MoveComponentUp(this)) {
                continue;
            }//end while
            return;
        }
        //else, keep on bottom
        while (ComponentUtility.MoveComponentDown(this)) {
            continue;
        }//end while
    }


    void OnDestroy() {
        gameObject.tag = "Untagged";
    }
#endif


    #region handlers
    public void OnBeginDrag(PointerEventData eventData) {
        if (_propagate_BeginDrag == false) {
            return;
        }

        Handle_onSomeParent<IInitializePotentialDragHandler>((parent) => { parent.OnInitializePotentialDrag(eventData); });
        Handle_onSomeParent<IBeginDragHandler>((parent) => { parent.OnBeginDrag(eventData); });
    }


    public void OnDrag(PointerEventData eventData) {
        if (_propagate_Drag == false) {
            return;
        }
        
        Handle_onSomeParent<IDragHandler>((parent) => { parent.OnDrag(eventData); });
    }



    public void OnEndDrag(PointerEventData eventData) {
        if (_propagate_EndDrag == false) {
            return;
        }

        Handle_onSomeParent<IEndDragHandler>((parent) => { parent.OnEndDrag(eventData); });
    }



    public void OnPointerDown(PointerEventData eventData) {
        if (_propagate_PointerDown == false) {
            return;
        }

        Handle_onSomeParent<IPointerDownHandler>((parent) => { parent.OnPointerDown(eventData); });
    }




    public void OnPointerUp(PointerEventData eventData) {
        if (_propagate_PointerUp == false) {
            return;
        }

        Handle_onSomeParent<IPointerUpHandler>((parent) => { parent.OnPointerUp(eventData); });
    }
    #endregion





    private void Handle_onSomeParent<T>(Action<T> action) where T : IEventSystemHandler {

        Transform parent = transform.parent;
        bool parentHandled = false;

        while (parent != null) {
            foreach (var component in parent.GetComponents<Component>()) {

                #region check if skip
                //if component is not T (T is interface).
                //For some reason  isAssignableFrom(typeof(T))  didn't work
                if ( component.GetType().GetInterface(typeof(T).ToString()) == null ) {
                    continue;
                }

                //skip if this component instance was mentioned in our "blacklisted component instances"
                if(_blacklisted_Components_IN_PARENTS.Any(c=>c == component)) {
                    continue;
                }

                //if the gameObject is not whitelisted, and the component is blacklisted:
                if( _whitelisted_gos.Any(whiteGO => whiteGO==component.gameObject) == false
                    && _blacklistedTypes_IN_PARENTS.Any(black=> black == component.GetType())  ) {
                    continue; //skip such a black-listed component
                }
                #endregion
              
                action((T)(IEventSystemHandler)component);
                parentHandled = true;
            }//end foreach component

            if (parentHandled) {
                //all components on the handle-able parent were fed:
                return;
            }

            //else, keep looking for parent who can handle such an event:
            parent = parent.parent;
        }//end while(parent != null)
    }





    bool isBlockingAll() {

         if(_propagate_Drag) {
            return false;
         }
         if(_propagate_BeginDrag) {
            return false;
        }
        if (_propagate_EndDrag) {
            return false;
        }

        if (_propagate_PointerDown) {
            return false;
        }
        if (_propagate_PointerUp) {
            return false;
        }

        return true;
    }



}
1 Like