You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
7.2 KiB
204 lines
7.2 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UnityEditor.VFX.UI
|
|
{
|
|
abstract class DropDownButtonBase : VisualElement
|
|
{
|
|
protected readonly VFXView m_VFXView;
|
|
|
|
internal class NotifyEditorWindow : EditorWindow
|
|
{
|
|
public event Action Destroyed;
|
|
|
|
private void OnDestroy()
|
|
{
|
|
Destroyed?.Invoke();
|
|
}
|
|
}
|
|
|
|
readonly bool m_HasLeftSeparator;
|
|
readonly Button m_MainButton;
|
|
|
|
NotifyEditorWindow m_CurrentPopup;
|
|
DateTime m_PopupClosedTimestamp;
|
|
|
|
protected readonly VisualElement m_PopupContent;
|
|
|
|
protected DropDownButtonBase(
|
|
string elementName,
|
|
VFXView view,
|
|
string uxmlSource,
|
|
string mainButtonLabel,
|
|
string mainButtonName,
|
|
string iconPath,
|
|
bool hasSeparatorBefore = false,
|
|
bool hasSeparatorAfter = false)
|
|
{
|
|
name = elementName;
|
|
m_VFXView = view;
|
|
style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
|
|
|
|
if (hasSeparatorBefore)
|
|
{
|
|
m_HasLeftSeparator = true;
|
|
var divider = new VisualElement();
|
|
divider.AddToClassList("separator");
|
|
Add(divider);
|
|
}
|
|
|
|
m_MainButton = new Button(OnMainButton) { name = mainButtonName };
|
|
m_MainButton.focusable = false;
|
|
m_MainButton.AddToClassList("dropdown-button");
|
|
m_MainButton.AddToClassList("unity-toolbar-toggle");
|
|
if (!string.IsNullOrEmpty(iconPath))
|
|
{
|
|
var icon = new Image { image = EditorGUIUtility.LoadIcon(iconPath) };
|
|
m_MainButton.Add(icon);
|
|
m_MainButton.tooltip = mainButtonLabel;
|
|
}
|
|
else
|
|
{
|
|
m_MainButton.text = mainButtonLabel;
|
|
}
|
|
Add(m_MainButton);
|
|
|
|
var separator = new VisualElement();
|
|
separator.AddToClassList("dropdown-separator");
|
|
Add(separator);
|
|
|
|
var dropDownButton = new Button(OnTogglePopup);
|
|
dropDownButton.focusable = false;
|
|
dropDownButton.AddToClassList("dropdown-arrow");
|
|
dropDownButton.AddToClassList("unity-toolbar-toggle");
|
|
dropDownButton.Add(new VisualElement());
|
|
Add(dropDownButton);
|
|
|
|
if (hasSeparatorAfter)
|
|
{
|
|
var divider = new VisualElement();
|
|
divider.AddToClassList("separator");
|
|
Add(divider);
|
|
}
|
|
|
|
m_PopupContent = new VisualElement();
|
|
var tpl = VFXView.LoadUXML(uxmlSource);
|
|
tpl.CloneTree(m_PopupContent);
|
|
contentContainer.AddStyleSheetPath("VFXToolbar");
|
|
}
|
|
|
|
protected virtual void OnOpenPopup() { }
|
|
protected virtual void OnMainButton() { }
|
|
protected abstract Vector2 GetPopupSize();
|
|
|
|
protected void ClosePopup()
|
|
{
|
|
m_CurrentPopup?.Close();
|
|
}
|
|
|
|
private Vector2 GetPopupPosition() => m_VFXView.ViewToScreenPosition(worldBound.position);
|
|
|
|
protected void OnTogglePopup()
|
|
{
|
|
// If the user click on the arrow button while the popup is opened
|
|
// the popup is then closed (because clicked outside) and immediately reopened
|
|
// To prevent this behavior we allow the popup to reopen only after a very short period of time after being closed
|
|
var deltaTime = DateTime.UtcNow - m_PopupClosedTimestamp;
|
|
if (m_CurrentPopup == null && deltaTime.TotalMilliseconds > 500)
|
|
{
|
|
m_CurrentPopup = ScriptableObject.CreateInstance<NotifyEditorWindow>();
|
|
m_CurrentPopup.hideFlags = HideFlags.HideAndDontSave;
|
|
if (m_PopupContent.parent != null)
|
|
{
|
|
m_PopupContent.parent.Remove(m_PopupContent);
|
|
}
|
|
|
|
m_CurrentPopup.Destroyed += OnPopupClosed;
|
|
m_CurrentPopup.rootVisualElement.AddStyleSheetPath("VFXToolbar");
|
|
m_CurrentPopup.rootVisualElement.Add(m_PopupContent);
|
|
m_CurrentPopup.rootVisualElement.AddToClassList("popup");
|
|
m_CurrentPopup.rootVisualElement.RegisterCallback<KeyUpEvent>(OnKeyUp);
|
|
|
|
OnOpenPopup();
|
|
var bounds = new Rect(GetPopupPosition(), localBound.size);
|
|
// Offset the bounds to align the popup with the real dropdown left edge
|
|
if (m_HasLeftSeparator)
|
|
{
|
|
bounds.xMin += 6;
|
|
}
|
|
|
|
m_CurrentPopup.ShowAsDropDown(bounds, GetPopupSize(), new[] { PopupLocation.BelowAlignLeft, PopupLocation.AboveAlignLeft });
|
|
m_CurrentPopup.minSize = GetPopupSize();
|
|
m_CurrentPopup.maxSize = m_CurrentPopup.minSize;
|
|
GetNextFocusable(null, m_PopupContent.Children(), false)?.Focus();
|
|
}
|
|
else if (m_CurrentPopup != null)
|
|
{
|
|
ClosePopup();
|
|
}
|
|
}
|
|
|
|
private void OnPopupClosed()
|
|
{
|
|
m_CurrentPopup.Destroyed -= OnPopupClosed;
|
|
m_CurrentPopup.rootVisualElement.UnregisterCallback<KeyUpEvent>(OnKeyUp);
|
|
m_CurrentPopup = null;
|
|
m_PopupClosedTimestamp = DateTime.UtcNow;
|
|
}
|
|
|
|
private void OnKeyUp(KeyUpEvent evt)
|
|
{
|
|
var focused = m_PopupContent.focusController.focusedElement;
|
|
switch (evt.keyCode)
|
|
{
|
|
case KeyCode.DownArrow:
|
|
var next = GetNextFocusable(focused, m_PopupContent.Children(), false);
|
|
next?.Focus();
|
|
break;
|
|
case KeyCode.UpArrow:
|
|
var prev = GetNextFocusable(focused, m_PopupContent.Children(), true);
|
|
prev?.Focus();
|
|
break;
|
|
case KeyCode.Escape:
|
|
ClosePopup();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private VisualElement GetNextFocusable(Focusable focused, IEnumerable<VisualElement> elements, bool reverse)
|
|
{
|
|
var found = focused == null;
|
|
return GetNextFocusableRecursive(focused, elements, reverse, ref found);
|
|
}
|
|
|
|
private VisualElement GetNextFocusableRecursive(Focusable focused, IEnumerable<VisualElement> elements, bool reverse, ref bool found)
|
|
{
|
|
var collection = reverse ? elements.Reverse() : elements;
|
|
foreach (var child in collection)
|
|
{
|
|
if (child == focused)
|
|
{
|
|
found = true;
|
|
continue;
|
|
}
|
|
|
|
if (found && child != focused && child.enabledSelf && child.focusable)
|
|
{
|
|
return child;
|
|
}
|
|
|
|
var next = GetNextFocusableRecursive(focused, child.Children(), reverse, ref found);
|
|
if (next != null)
|
|
{
|
|
return next;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|