Is there is a way to block mouse hover events outside current focused Editor window rect in Unity Editor?

Viewed 36

I am working on custom enum popup with search and facing next problem. When clicking button to show popup list of enums I can hover over that list and select items inside, but also I can as always move mouse outside popup rect and move around. And when I hover over other GUI element it reacts to that movement (hover highlight or so happens). I remembered that default Unity's enum don't have this feature and it blocks all events outside popup's rect exclude outside mouse click to close this popup.

So... My question is: How can I implement this feature in my popup window? I look into Unity's source code and don't find anything related to this. Except one moment when they are using internal function "GUI.GrabMouseControl(id);"

There are this internal function for enum button where they block mouse control somehow:

 // A button that returns true on mouse down - like a popup button
    internal static bool DropdownButton(int id, Rect position, GUIContent content, GUIStyle style)
    {
        Event evt = Event.current;
        switch (evt.type)
        {
            case EventType.Repaint:
                var hovered = position.Contains(Event.current.mousePosition);
                if (showMixedValue)
                {
                    BeginHandleMixedValueContentColor();
                    style.Draw(position, s_MixedValueContent, id, false, hovered);
                    EndHandleMixedValueContentColor();
                }
                else
                    style.Draw(position, content, id, false, hovered);
                break;
            case EventType.MouseDown:
                if (GUIUtility.HitTest(position, evt) && evt.button == 0)
                {
                    GUI.GrabMouseControl(id);
                    Event.current.Use();
                    return true;
                }
                break;
            case EventType.MouseUp:
                if (GUI.HasMouseControl(id))
                {
                    GUI.ReleaseMouseControl();
                    Event.current.Use();
                }
                break;
            case EventType.KeyDown:
                if (GUIUtility.keyboardControl == id && evt.character == ' ')
                {
                    Event.current.Use();
                    return true;
                }
                break;
        }
        return false;
    }

I think that may be that magic thing to block mouse hovering. Also I think that GUIUtility.hotControll is second canditate to solve my issue. But I dont know how properly use it in my OnGUI() method. I am trying to it and get not correct result, but I blocked hovering outside popup's rect. But it always spam console with these logs: Should not grab hot control with an active capture. So I dropped this idea with my implementation, cause of these logs...

There are my code of popup window content:

using System;

using UnityEditor; using UnityEngine;

public class SearchablePopup : PopupWindowContent { #region Popup Properties

private const float SymbolVerticalOffset = 0.75f;
private const int ClearButtonSize = 12;

private const string SearchControl = "searchablePopup_ctrl";

#endregion

public static void Show(Rect from, Type type, int current,
    Action<int> onSelectionMade, int elementsToShow, int elementHeight,
    int searchHeight, Texture backgroundTexture,
    GUIStyle searchStyle, GUIStyle clearButtonStyle, GUIStyle elementStyle,
    Color hover, Color selected, Color marker)
{
    if (type.IsEnum is false) return;

    var window = new SearchablePopup(type, current, onSelectionMade,
        (int)from.width, elementsToShow, elementHeight, searchHeight,
        backgroundTexture, searchStyle, clearButtonStyle, elementStyle,
        hover, selected, marker);


    PopupWindow.Show(from, window);
}

private readonly FilteredList _filteredList;

private readonly Action<int> _onSelectionMade;
private readonly int _width;
private readonly int _elementsToShow;
private readonly int _rowHeight;
private readonly int _searchHeight;

private readonly float _clearButtonPadding;
private readonly bool _clearButtonValid;

private readonly GUIStyle _searchStyle;
private readonly GUIStyle _searchTooltipStyle;
private readonly GUIStyle _clearButtonStyle;
private readonly GUIStyle _elementStyle;

private readonly Texture _backgroundTexture;

private readonly Texture _hoverTexture;
private readonly Texture _selectedTexture;
private readonly Texture _selectedCircleTexture;

private int _current;
private int _hover;
private int _keyboardIndex;
private int _scroll;

private int _controlHash = "SearchablePopup".GetHashCode();

private Vector2 _scrollPosition;

private Vector2 _clickPosition;
private bool _clickedThisFrame;

public SearchablePopup(Type enumType, int current, Action<int> onSelectionMade,
    int width, int elementsToShow, int rowHeight, int searchHeight, Texture backgroundTexture,
    GUIStyle searchStyle, GUIStyle clearButtonStyle, GUIStyle elementStyle,
    Color hover, Color selected, Color marker)
{
    _filteredList = new FilteredList(enumType, current);

    _hoverTexture = SmallTexture(hover);
    _selectedTexture = SmallTexture(selected);
    _selectedCircleTexture = DrawCircle(4, marker);

    _current = current;
    _hover = _current;
    _scroll = _current;
    _keyboardIndex = _filteredList.CurrentIndex;

    _onSelectionMade = onSelectionMade;

    _elementsToShow = elementsToShow;
    _width = width;
    _rowHeight = rowHeight;
    _searchHeight = searchHeight;

    _backgroundTexture = backgroundTexture;
    _searchStyle = searchStyle;

    _clearButtonValid = clearButtonStyle != null;
    _clearButtonStyle = clearButtonStyle;
    _clearButtonPadding = _clearButtonValid ? clearButtonStyle.padding.left : 4;
    _elementStyle = elementStyle;

    if (_clearButtonValid is false)
    {
        _elementStyle = new GUIStyle(_elementStyle);
        _elementStyle.alignment = TextAnchor.MiddleLeft;
    }

    _searchTooltipStyle = new GUIStyle(_searchStyle);
    _searchTooltipStyle.normal.textColor *= 0.75f;
    _searchTooltipStyle.hover.textColor = _searchTooltipStyle.normal.textColor;
    _searchTooltipStyle.fontSize = (int)(_searchTooltipStyle.fontSize * 0.75f);
}

public override Vector2 GetWindowSize()
{
    float height = Mathf.Min(
        _filteredList.Entries.Count, _elementsToShow) * _rowHeight
        + _searchHeight;

    //Strange hot-fix for Unity's Scrollview:
    //We are increasing total height of our window's rect
    //to be a litte bigger than actual content because
    //if Scrollview's height >= Window's height than scrollbar is displayed
    //what we don't want to see if our current count of entires is less than show amount
    if (_filteredList.Entries.Count < _elementsToShow)
        height += 0.25f;

    return new Vector2(_width, height);
}

public override void OnOpen()
{
    base.OnOpen();
    EditorApplication.update += Repaint;
}

public override void OnClose()
{
    base.OnClose();
    EditorApplication.update -= Repaint;
}

private void Repaint()
{
    EditorWindow.focusedWindow.Repaint();
}

public override void OnGUI(Rect rect)
{
    GUI.DrawTexture(rect, _backgroundTexture);

    if (Event.current.type == EventType.MouseDown)
    {
        _clickedThisFrame = true;
        _clickPosition = Event.current.mousePosition;
    }

    Rect search = new Rect(0, 0, rect.width, _searchHeight);
    Rect clear = Rect.MinMaxRect(search.xMax - ClearButtonSize - _clearButtonPadding,
        search.center.y - ClearButtonSize * 0.5f, rect.xMax - _clearButtonPadding,
        search.center.y + ClearButtonSize * 0.5f);
    Rect scroll = Rect.MinMaxRect(0, search.yMax, rect.xMax, rect.yMax);


    HandleKeyboardInput();
    DrawSearch(search, clear);
    DrawScroll(scroll);

    _clickedThisFrame = false;
}

private void DrawSearch(Rect search, Rect clear)
{
    GUI.FocusControl(SearchControl);
    GUI.SetNextControlName(SearchControl);
    var filter = TextfieldWithTooltip(search, _filteredList.Filter,
        "Type to search...", _searchStyle, _searchTooltipStyle);

    if (_filteredList.Refresh(filter))
    {
        _scrollPosition = Vector2.zero;
    }

    if (string.IsNullOrEmpty(_filteredList.Filter) is false)
    {
        if (_clearButtonValid)
        {
            GUI.Box(clear, GUIContent.none, _clearButtonStyle);
        }
        else
        {
            GUI.Box(clear, "x", GUI.skin.label);
        }

        if (_clickedThisFrame && clear.Contains(_clickPosition))
        {
            _filteredList.Refresh("");
            _scrollPosition = Vector2.zero;
        }
    }

}

private void DrawScroll(Rect scroll)
{
    Rect contentRect = new Rect(0, 0,
        scroll.width - GUI.skin.verticalScrollbar.fixedWidth,
        _filteredList.Entries.Count * _rowHeight);

    _scrollPosition = GUI.BeginScrollView(scroll, _scrollPosition, contentRect);

    Rect element = new Rect(0, 0, scroll.width, _rowHeight);

    var eventType = Event.current.type;

    for (int i = 0; i < _filteredList.Entries.Count; i++)
    {
        if (_scroll == _filteredList.Entries[i].Value && (eventType == EventType.Repaint || eventType == EventType.Layout))
        {
            GUI.ScrollTo(element);
            _scrollPosition.x = 0;
            _scroll = -1;
        }

        if (element.Contains(Event.current.mousePosition))
        {
            if (Event.current.type == EventType.MouseMove ||
                Event.current.type == EventType.ScrollWheel)
            {
                _hover = _filteredList.Entries[i].Value;
                _keyboardIndex = i;
            }

            if (Event.current.type == EventType.MouseDown)
            {
                _onSelectionMade(_filteredList.Entries[i].Value);
                EditorWindow.focusedWindow.Close();
            }
        }

        DrawElement(element, _filteredList.Entries[i].Value, i);
        element.y = element.yMax;
    }

    GUI.EndScrollView();
}

private void DrawElement(Rect rect, int value, int index)
{
    if (value == _current)
    {
        DrawBox(rect, _selectedTexture, _selectedCircleTexture);

        rect.xMin += _selectedCircleTexture.width * 2f;
    }
    else if (value == _hover)
    {
        DrawBox(rect, _hoverTexture);
    }

    GUI.Label(rect, _filteredList.Entries[index].Content, _elementStyle);
}

private void DrawBox(Rect rect, Texture texture, Texture symbol = null)
{
    GUI.DrawTexture(rect, texture);
    if (symbol == null) return;

    rect.xMin += symbol.width * 0.5f;
    rect.y = rect.center.y - symbol.height * 0.5f + SymbolVerticalOffset;

    rect.width = symbol.width;
    rect.height = symbol.height;


    GUI.DrawTexture(rect, symbol);
}

private void HandleKeyboardInput()
{
    if (Event.current.isKey is false || Event.current.type != EventType.KeyDown) return;

    var keyCode = Event.current.keyCode;

    if (keyCode == KeyCode.Escape)
    {
        EditorWindow.focusedWindow.Close();
        return;
    }

    if (keyCode == KeyCode.Return)
    {
        _onSelectionMade(_filteredList.Entries[_keyboardIndex].Value);
        EditorWindow.focusedWindow.Close();
        return;
    }

    if (keyCode == KeyCode.DownArrow)
    {
        _keyboardIndex = Mathf.Min(_filteredList.Entries.Count - 1, _keyboardIndex + 1);
        _hover = _filteredList.Entries[_keyboardIndex].Value;

        Event.current.Use();

        _scroll = _hover;
    }
    else if (keyCode == KeyCode.UpArrow)
    {
        _keyboardIndex = Mathf.Max(0, _keyboardIndex - 1);
        _hover = _filteredList.Entries[_keyboardIndex].Value;

        GUIUtility.hotControl = 0;
        Event.current.Use();

        _scroll = _hover;
    }

}

#region Helpers

public static Rect Shrink(Rect rect, float amount)
{
    return new Rect(rect.x + amount, rect.y + amount, rect.width - amount * 2, rect.height - amount * 2);
}

public static Texture SmallTexture(string hex)
{
    Texture2D texture = new Texture2D(4, 4);
    var color = HexToRGB(hex);

    for (int i = 0; i < texture.width; i++)
    {
        for (int j = 0; j < texture.height; j++)
        {
            texture.SetPixel(i, j, color);
        }
    }

    texture.Apply();
    return texture;
}

public static Texture SmallTexture(Color color)
{
    Texture2D texture = new Texture2D(4, 4);

    for (int i = 0; i < texture.width; i++)
    {
        for (int j = 0; j < texture.height; j++)
        {
            texture.SetPixel(i, j, color);
        }
    }

    texture.Apply();
    return texture;
}

public static Texture DrawCircle(int radius, string hex)
{
    Texture2D texture = new Texture2D(radius * 2, radius * 2);
    var color = HexToRGB(hex);

    float cX = radius;
    float cY = radius;
    float sizeSquared = radius * radius;

    for (int i = 0; i < texture.width; i++)
    {
        for (int j = 0; j < texture.height; j++)
        {
            if ((cX - i) * (cX - i) + (cY - j) * (cY - j) < sizeSquared)
            {
                texture.SetPixel(i, j, color);
            }
            else texture.SetPixel(i, j, Color.clear);
        }
    }

    texture.Apply();
    return texture;
}

public static Texture DrawCircle(int radius, Color color)
{
    Texture2D texture = new Texture2D(radius * 2, radius * 2);

    float cX = radius;
    float cY = radius;
    float sizeSquared = radius * radius;

    for (int i = 0; i < texture.width; i++)
    {
        for (int j = 0; j < texture.height; j++)
        {
            if ((cX - i) * (cX - i) + (cY - j) * (cY - j) < sizeSquared)
            {
                texture.SetPixel(i, j, color);
            }
            else texture.SetPixel(i, j, Color.clear);
        }
    }

    texture.Apply();
    return texture;
}

public static Color HexToRGB(string hex)
{
    hex = hex.TrimStart('#');

    if (hex.Length != 6) return Color.black;

    int r = Int16.Parse(hex.Substring(0, 2),
        System.Globalization.NumberStyles.AllowHexSpecifier);
    int g = Int16.Parse(hex.Substring(2, 2),
        System.Globalization.NumberStyles.AllowHexSpecifier);
    int b = Int16.Parse(hex.Substring(4, 2),
        System.Globalization.NumberStyles.AllowHexSpecifier);

    return new Color(r / 255f, g / 255f, b / 255f);
}

public static string TextfieldWithTooltip(Rect rect, string text, string tooltip,
       GUIStyle textfieldStyle, GUIStyle tooltipStyle)
{
    text = GUI.TextField(rect, text, textfieldStyle);

    if (text.Length == 0)
        GUI.Label(rect, tooltip, tooltipStyle);

    return text;
}

#endregion

}

I will be realy glad to see some advices or straight anwsers on this problem is someone can solve it :) Thank you!

0 Answers
Related