summaryrefslogtreecommitdiffstats
path: root/Scripts/Editor/LinuxTabFix.cs
blob: 0fa322875acba6ad029dfe5873cb456ffd9dc1ea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// Source: https://gist.githubusercontent.com/s-ilent/067d4e477041d52d2f8eabd78927d1a4/raw/568c222044a8ce3c33426c495cccbfe7c3a0c733/LinuxTabFix.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

/// <summary>
/// Linux Editor Tab Navigation Patch for Unity (2022.3+, Unity 6).
/// Restores missing ASCII '\t' character codes stripped by Linux window backend
/// and manages focus handshakes between IMGUIContainers in UI Toolkit windows.
/// </summary>
[InitializeOnLoad]
public static class LinuxTabFix
{
    private static readonly ConditionalWeakTable<IMGUIContainer, object> patchedContainers 
        = new ConditionalWeakTable<IMGUIContainer, object>();

    static LinuxTabFix()
    {
        // Ensure idempotent event subscription across domain reloads
        EditorApplication.update -= ApplyTabPatchToFocusedWindow;
        EditorApplication.update += ApplyTabPatchToFocusedWindow;
    }

    private static void ApplyTabPatchToFocusedWindow()
    {
        EditorWindow focusedWindow = EditorWindow.focusedWindow;
        if (focusedWindow == null) return;

        VisualElement root = focusedWindow.rootVisualElement;
        if (root == null) return;

        List<IMGUIContainer> containers = root.Query<IMGUIContainer>().ToList();
        int count = containers.Count;

        for (int i = 0; i < count; i++)
        {
            IMGUIContainer container = containers[i];
            if (container == null) continue;

            int currentIndex = i;

            // Patch unhandled containers without modifying container properties
            if (!patchedContainers.TryGetValue(container, out _))
            {
                patchedContainers.Add(container, null);
                Action originalHandler = container.onGUIHandler;

                container.onGUIHandler = () =>
                {
                    Event e = Event.current;

                    if (e != null && e.type == EventType.KeyDown && e.keyCode == KeyCode.Tab)
                    {
                        // Restore character code stripped by Linux native window backend
                        if (e.character == 0)
                        {
                            e.character = '\t';
                        }

                        bool isShift = e.shift;

                        // Execute original IMGUI pass
                        originalHandler?.Invoke();

                        // When boundary is reached, transfer focus to adjacent container
                        if (GUIUtility.keyboardControl == 0)
                        {
                            int targetIndex = isShift ? currentIndex - 1 : currentIndex + 1;

                            if (targetIndex >= 0 && targetIndex < count && targetIndex < containers.Count)
                            {
                                IMGUIContainer targetContainer = containers[targetIndex];
                                if (targetContainer != null)
                                {
                                    targetContainer.Focus();
                                    focusedWindow.Repaint();
                                }
                            }
                        }

                        return;
                    }

                    originalHandler?.Invoke();
                };
            }
        }
    }
}
#endif