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

Capturing KeyDownEvents in EditorWindow and Focus

Discussion in 'UI Toolkit' started by Tom-Atom, Oct 16, 2019.

  1. Tom-Atom

    Tom-Atom

    Joined:
    Jun 29, 2014
    Posts:
    153
    Hi, I want to capture all KeyDownEvents when my EditorWindow is opened. I found, that by default rootVisualElement of EditrWIndow has .focusable set to false. So when creating EditorWindow, I do this:

    Code (CSharp):
    1.     [MenuItem("Level Editor/Editor")]
    2.     public static void CreateEditorWindow() {
    3.        
    4.         LevelDefEditor window = GetWindow<LevelDefEditor>();
    5.         window.minSize = new Vector2(1000, 400);
    6.         window.titleContent = new GUIContent("My Editor");
    7.  
    8.         // make root element focusable
    9.         window.rootVisualElement.focusable = true;
    10.     }
    To capture KeyDownEvents I do this (inside OnEnable):
    Code (CSharp):
    1.         // kayboard handling
    2.         rootVisualElement.RegisterCallback<KeyDownEvent>(evt => {
    3.            
    4.             Debug.Log($"Pressed key: {evt.keyCode}, character: {evt.character}");
    5.  
    6.             evt.StopImmediatePropagation();
    7.         });
    I found this is not enough. I had to add this code into OnFocus:
    Code (CSharp):
    1.     public void OnFocus() {
    2.  
    3.         rootVisualElement.Focus();
    4.     }
    Now, when I open my Editor window from top Unity menu, it gets focus and consumes all key down events. But, when I click outside of it - for example into scene view - and then back it runs OnFocus handler, but rootVisualElement.Focus(); is ignored. Editor window does not get focus back and key events are consumed by scene view. Editor window has its title tab highlighted, but rootVisualElement does not have focus (I can see, that in UIElements debugger list of Psuedo States does not contain "Focus")

    After lot of struggles I found, that if I change OnFocus to this:
    Code (CSharp):
    1.     public void OnFocus() {
    2.  
    3.         rootVisualElement.schedule.Execute(() => {
    4.             rootVisualElement.Focus();
    5.         });
    6.     }
    it started to work.

    Is it correct way how to get focus for root element of window? Or is it bug? It looks, like small postponing is needed, otherwise it does not work...
     
  2. patrickf

    patrickf

    Unity Technologies

    Joined:
    Oct 24, 2016
    Posts:
    57
    Hi!

    UIElements already restores the focus to the last focused element when a window gets the focus. You can see this if you comment out your OnFocus() and use Ctrl-tab to get the focus back to your window.

    The problem is when you click in your window, you focus the #DockArea underneath the rootVisualElement because #DockArea is focusable and has pickingMode == PickingMode.Position.

    The rootVisualElement has its pickingMode set to PickingMode.Ignore, so it is never the target of mouse events, so it does not get the focus when you click in it. Just change it to PickingMode.Position and everything should work, except if you click in the tab well, which is not covered by the rootVisualElement.
     
  3. Tom-Atom

    Tom-Atom

    Joined:
    Jun 29, 2014
    Posts:
    153
    @patrickf thanks for answer!

    Unfortunately, it did not solve these issues:
    - when editor is first open from menu (there is no click)
    - when clicking on editor tab (as you mentioned)

    I wanted to solve it, as other person than me working with editor may complain it doesn't work.

    I found, I can get access to dock area. So, I set MouseDownEvent handler to it. This is my current setup:
    Code (CSharp):
    1.     // ---------------------------------------------------------
    2.     [MenuItem("Level Editor/Editor")]
    3.     public static void CreateEditorWindow() {
    4.      
    5.         LevelDefEditor window = GetWindow<LevelDefEditor>();
    6.         window.minSize = new Vector2(1000, 400);
    7.         window.titleContent = new GUIContent("My Editor");
    8.  
    9.         // make root element focusable
    10.         window.rootVisualElement.focusable = true;
    11.  
    12.         VisualElement panel = window.rootVisualElement.panel.visualTree;
    13.         panel.ElementAt(0).RegisterCallback<MouseDownEvent>((evt) => {
    14.             window.GiveFocusToEditor();
    15.         });
    16.     }
    17.  
    18.     // ---------------------------------------------------------
    19.     public void OnFocus() {
    20.  
    21.         GiveFocusToEditor();
    22.     }
    23.  
    24.     // ---------------------------------------------------------
    25.     public void GiveFocusToEditor() {
    26.  
    27.         rootVisualElement.schedule.Execute(() => {
    28.             rootVisualElement.Focus();
    29.         });
    30.     }
    It gives focus to editor window, so it consumes all keys as I needed, when:
    • it is first opened from menu,
    • I click into different window (for example scene view) and then back inside editor window,
    • I click into different window and then back on dock area of editor window
     
  4. Anisoft

    Anisoft

    Joined:
    Jul 5, 2013
    Posts:
    137
    Did this end up solving the issue you had? I'm having trouble with focus and key events myself
     
  5. Tom-Atom

    Tom-Atom

    Joined:
    Jun 29, 2014
    Posts:
    153
    @Anisoft Yes, it solved my issues and window consumed all key inputs.
     
  6. TenaciousDan

    TenaciousDan

    Joined:
    Jul 11, 2019
    Posts:
    22
    This line solved the issue of capturing mousedown events within the window's dock area (empty area):
    Code (CSharp):
    1. rootVisualElement.pickingMode = PickingMode.Position;
    Although it did not solve the issue when user clicks in the title bar area of the window.

    After some fiddling around, I found a better solution, and since this is one of the top results when googling this issue, I am posting my solution here.

    All you need is the following code to handle keydown events:
    Code (CSharp):
    1.  
    2. void CreateGUI()
    3. {
    4.     // ...
    5.     rootVisualElement.focusable = true;
    6.     rootVisualElement.pickingMode = PickingMode.Position;
    7.     rootVisualElement.RegisterCallback<KeyDownEvent>(HandleRootElementKeyDownEvents);
    8.  
    9.     Focus();
    10. }
    11.  
    12. void OnFocus()
    13. {
    14.     // ...
    15.     rootVisualElement.Focus();
    16. }
    17.  
    18. void OnGUI()
    19. {
    20.     // handles the case where the window is focused and the user's mouse is in the window's titlebar area
    21.     if (focusedWindow == this && Event.current != null && (Event.current.type == EventType.Layout && Event.current.mousePosition.y < 0))
    22.         rootVisualElement.Focus();
    23. }
    24.  
    25. void HandleRootElementKeyDownEvents(KeyDownEvent evt)
    26. {
    27.     if (evt == null)
    28.         return;
    29.  
    30.     bool propagate = true;
    31.  
    32.     // handle keydown events using evt variable...
    33.  
    34.     if (!propagate)
    35.     {
    36.         evt.StopPropagation();
    37.         evt.imguiEvent?.Use();
    38.     }
    39. }
    40.  
    Important Note:
    - To ensure keydown event shortcuts always work when using this editor window:
    - If you add any UI elements, make sure that if they are focusable then they return focus to last focused element when focus is not needed anymore. Otherwise, either set focusable = false or register the same keydown event handler as the rootVisualElement during setup (CreateGUI).
     
    Tom-Atom likes this.