Search Unity

GUI.Window: resolving flicker, preventing click-thru, understanding events

Discussion in 'Immediate Mode GUI (IMGUI)' started by Pi_3-14, Feb 16, 2013.

  1. Pi_3-14

    Pi_3-14

    Joined:
    May 9, 2012
    Posts:
    168
    I'm using GUILayout.Window to display my game settings. I want the window to size itself according to the content (so if I expand a pulldown, the window must accommodate). Additionally I want to prevent mouse clicks from falling through the GUI into the game.

    I have solved the problem, but I'd like to present my code in case someone can improve upon it, also it may be useful to someone.

    First problem: dynamic resizing of the window without getting flickering

    First problem I noticed is that when I (e.g.) drag a GUILayout.HorizontalSlider the window flickers. I worked out that this is because in my call to create a GUILayout.Window, I am passing a rectangle ( origin.x, origin.y, 0, 0 ) and the window automatically adjusts its size according to content.

    What I get is a flickering between a zero size window and the correct size. If instead I used ( origin.x, origin.y, 5000, 5000 ), I now get a flickering between a giant window and the correct size.

    GUILayout.Window doc says:
    So I should be able to just remember the window size the first time it is generated, and just feed that rectangle into subsequent frames.

    But it isn't quite that simple.

    Experimenting, I noticed that I get the correct Rect returned iff (if and only if) '( Event.current.type == EventType.Repaint )'.

    This is against my intuition; surely the entire purpose of the EventType.Layout event is to figure out the rectangle dimensions of GUI objects? So I would expect EventType.Layout event to return the correct rectangle, so that when we get the EventType.Repaint event, I can feed in this correct rectangle.

    Also I'm getting a lot of events that are neither EventType.Layout nor EventType.Repaint. Is there some resource that offers a top-down understanding of how events mingle with GUI?


    Second problem: preventing click-thru
    There doesn't seem to be a definitive way to solve this.
    What I've done is, every Update( ), check to see whether the mouse is inside the GUILayout.Window's rectangle, and expose a public bool. Then from my game I have to modify all my click handlers: 'if( click() ! MyGUI.mouseOverGUI ) {... }

    Anyway, here's the code:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class SettingsGUI : MonoBehaviour
    6. {
    7.     private static int SETTINGS_WINDOW_ID = 1;
    8.    
    9.     static bool m_mouseOverWindow = false;
    10.    
    11.     static public bool MouseOverGUI { get { return m_mouseOverWindow; } }
    12.    
    13.     Rect guiRect;
    14.    
    15.     bool isShowing = false;
    16.     void OnGUI( )
    17.     {
    18.         isShowing = GUILayout.Toggle( isShowing, "ALL Settings" );
    19.        
    20.         if( isShowing )
    21.         {
    22.             // offset of window from top left corner of the screen
    23.             Vector2 origin = new Vector2( 90, 90 );
    24.            
    25.             float windowWidth = Screen.width - 2 * origin.x;
    26.            
    27.             // this is necessary to prevent the window flickering when user
    28.             //   (e.g.) adjusts a slider bar
    29.             Rect R0 = new Rect( origin.x, origin.y, 0, 0 );
    30.             Rect R_in = ( Event.current.type == EventType.Layout ) ?
    31.                 R0 : guiRect;
    32.            
    33.             Rect R_out = GUILayout.Window(
    34.                 SETTINGS_WINDOW_ID,
    35.                 R_in,
    36.                 WindowFunc,
    37.                 "All-settings-window!",
    38.                 GUILayout.Width( windowWidth )
    39.                 );
    40.            
    41.             // whenever the window changes size, store the new size
    42.             //  (so that we can feed the correct size into the above
    43.             //  window generation, thus avoiding flickering)
    44.             //
    45.             // NOTE: Each frame potentially several events call OnGUI.
    46.             //  Only Repaint guarantees that GUILayout.Window will return the correct dimensions
    47.             if( Event.current.type == EventType.Repaint )
    48.                 if( ! guiRect.Equals( R_out ) )
    49.                     guiRect = R_out;
    50.         }
    51.     }
    52.    
    53.     void Update( )
    54.     {
    55.         Vector2 mousePos = new Vector2(
    56.                             Input.mousePosition.x,
    57.             Screen.height - Input.mousePosition.y
    58.             );
    59.  
    60.         m_mouseOverWindow = isShowing  guiRect.Contains( mousePos );
    61.     }
    62.    
    63.     void WindowFunc( int windowId )
    64.     { ... }
     
    Last edited: Feb 16, 2013
  2. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Posts:
    1,410
    Yes, this is the right way to do it. I did it the same way with eDriven (having a bit optimized version where I subscribe to mouse moves and only then checking the component below the mouse pointer).

    Your version is more robust in the scenario where a GUI element "flies in" below the mouse pointer (which doesn't move). :)

    With eDriven this would be written asynchronously as:

    Code (csharp):
    1. eDriven.Core.Managers.SystemManager.MouseMoveSignal.Connect(MouseMoveSlot);
    Code (csharp):
    1. public static bool MouseOverWindow;
    2. private void MouseMoveSlot(params object[] parameters)
    3. {
    4.         Point position = (Point)parameters[1];        
    5.         MouseOverWindow = // check if a mouse position [X, Y] is over a GUI component
    6. }
     
  3. usernameHed

    usernameHed

    Joined:
    Apr 5, 2016
    Posts:
    93
    I May be found a way, at the end of you function which get called by GUI.Window,
    add that code:

    Code (CSharp):
    1. if ((Event.current.type == EventType.MouseDrag || Event.current.type == EventType.MouseDown)
    2.             && Event.current.button == 0)
    3.         {
    4.             Debug.Log("here clic");
    5.            
    6.             Event.current.Use();  //Eat the event so it doesn't propagate through the editor.
    7.         }
    It works well for me: if we click inside the GUI.Window window, it doesn't go thought.