You're breathtaking!
This commit is contained in:
387
Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
Normal file
387
Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
Normal file
@@ -0,0 +1,387 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Assertions;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.GUI.ContextMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Context menu popup directions.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public enum ContextMenuDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// The right down.
|
||||
/// </summary>
|
||||
RightDown,
|
||||
|
||||
/// <summary>
|
||||
/// The right up.
|
||||
/// </summary>
|
||||
RightUp,
|
||||
|
||||
/// <summary>
|
||||
/// The left down.
|
||||
/// </summary>
|
||||
LeftDown,
|
||||
|
||||
/// <summary>
|
||||
/// The left up.
|
||||
/// </summary>
|
||||
LeftUp,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all context menu controls.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
[HideInEditor]
|
||||
public class ContextMenuBase : ContainerControl
|
||||
{
|
||||
private ContextMenuDirection _direction;
|
||||
private ContextMenuBase _parentCM;
|
||||
private bool _isSubMenu;
|
||||
private ContextMenuBase _childCM;
|
||||
private Window _window;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if context menu is opened
|
||||
/// </summary>
|
||||
public bool IsOpened => Parent != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the popup direction.
|
||||
/// </summary>
|
||||
public ContextMenuDirection Direction => _direction;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any child context menu has been opened.
|
||||
/// </summary>
|
||||
public bool HasChildCMOpened => _childCM != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the topmost context menu.
|
||||
/// </summary>
|
||||
public ContextMenuBase TopmostCM
|
||||
{
|
||||
get
|
||||
{
|
||||
var cm = this;
|
||||
while (cm._parentCM != null && cm._isSubMenu)
|
||||
{
|
||||
cm = cm._parentCM;
|
||||
}
|
||||
return cm;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this context menu is a sub-menu. Sub menus are treated like child context menus of the other menu (eg. hierarchy).
|
||||
/// </summary>
|
||||
public bool IsSubMenu => _isSubMenu;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContextMenuBase"/> class.
|
||||
/// </summary>
|
||||
public ContextMenuBase()
|
||||
: base(0, 0, 120, 32)
|
||||
{
|
||||
_direction = ContextMenuDirection.RightDown;
|
||||
Visible = false;
|
||||
|
||||
_isSubMenu = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show context menu over given control.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent control to attach to it.</param>
|
||||
/// <param name="location">Popup menu origin location in parent control coordinates.</param>
|
||||
public virtual void Show(Control parent, Vector2 location)
|
||||
{
|
||||
Assert.IsNotNull(parent);
|
||||
|
||||
// Ensure to be closed
|
||||
Hide();
|
||||
|
||||
// Peek parent control window
|
||||
var parentWin = parent.RootWindow;
|
||||
if (parentWin == null)
|
||||
return;
|
||||
|
||||
// Check if show menu inside the other menu - then link as a child to prevent closing the calling menu window on lost focus
|
||||
if (_parentCM == null && parentWin.ChildrenCount == 1 && parentWin.Children[0] is ContextMenuBase parentCM)
|
||||
{
|
||||
parentCM.ShowChild(this, parentCM.ScreenToClient(parent.ClientToScreen(location)), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlock and perform controls update
|
||||
UnlockChildrenRecursive();
|
||||
PerformLayout();
|
||||
|
||||
// Calculate popup direction and initial location (fit on a single monitor)
|
||||
var dpiScale = Platform.DpiScale;
|
||||
Vector2 dpiSize = Size * dpiScale;
|
||||
Vector2 locationWS = parent.PointToWindow(location);
|
||||
Vector2 locationSS = parentWin.ClientToScreen(locationWS * dpiScale);
|
||||
Location = Vector2.Zero;
|
||||
Rectangle monitorBounds = Platform.GetMonitorBounds(locationSS);
|
||||
Vector2 rightBottomLocationSS = locationSS + dpiSize;
|
||||
bool isUp = false, isLeft = false;
|
||||
if (monitorBounds.Bottom < rightBottomLocationSS.Y)
|
||||
{
|
||||
// Direction: up
|
||||
isUp = true;
|
||||
locationSS.Y -= dpiSize.Y;
|
||||
}
|
||||
if (monitorBounds.Right < rightBottomLocationSS.X)
|
||||
{
|
||||
// Direction: left
|
||||
isLeft = true;
|
||||
locationSS.X -= dpiSize.X;
|
||||
}
|
||||
|
||||
// Update direction flag
|
||||
if (isUp)
|
||||
_direction = isLeft ? ContextMenuDirection.LeftUp : ContextMenuDirection.RightUp;
|
||||
else
|
||||
_direction = isLeft ? ContextMenuDirection.LeftDown : ContextMenuDirection.RightDown;
|
||||
|
||||
// Create window
|
||||
var desc = CreateWindowSettings.Default;
|
||||
desc.Position = locationSS;
|
||||
desc.StartPosition = WindowStartPosition.Manual;
|
||||
desc.Size = dpiSize;
|
||||
desc.Fullscreen = false;
|
||||
desc.HasBorder = false;
|
||||
desc.SupportsTransparency = false;
|
||||
desc.ShowInTaskbar = false;
|
||||
desc.ActivateWhenFirstShown = true;
|
||||
desc.AllowInput = true;
|
||||
desc.AllowMinimize = false;
|
||||
desc.AllowMaximize = false;
|
||||
desc.AllowDragAndDrop = false;
|
||||
desc.IsTopmost = true;
|
||||
desc.IsRegularWindow = false;
|
||||
desc.HasSizingFrame = false;
|
||||
OnWindowCreating(ref desc);
|
||||
_window = Platform.CreateWindow(ref desc);
|
||||
_window.LostFocus += OnWindowLostFocus;
|
||||
|
||||
// Attach to the window
|
||||
_parentCM = parent as ContextMenuBase;
|
||||
Parent = _window.GUI;
|
||||
|
||||
// Show
|
||||
Visible = true;
|
||||
if (_window == null)
|
||||
return;
|
||||
_window.Show();
|
||||
PerformLayout();
|
||||
Focus();
|
||||
OnShow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide popup menu and all child menus.
|
||||
/// </summary>
|
||||
public virtual void Hide()
|
||||
{
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
// Lock update
|
||||
IsLayoutLocked = true;
|
||||
|
||||
// Close child
|
||||
HideChild();
|
||||
|
||||
// Unlink from window
|
||||
Parent = null;
|
||||
|
||||
// Close window
|
||||
if (_window != null)
|
||||
{
|
||||
var win = _window;
|
||||
_window = null;
|
||||
win.Close();
|
||||
}
|
||||
|
||||
// Unlink from parent
|
||||
if (_parentCM != null)
|
||||
{
|
||||
_parentCM._childCM = null;
|
||||
_parentCM = null;
|
||||
}
|
||||
|
||||
// Hide
|
||||
Visible = false;
|
||||
OnHide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows new child context menu.
|
||||
/// </summary>
|
||||
/// <param name="child">The child menu.</param>
|
||||
/// <param name="location">The child menu initial location.</param>
|
||||
/// <param name="isSubMenu">True if context menu is a normal sub-menu, otherwise it is a custom menu popup linked as child.</param>
|
||||
public void ShowChild(ContextMenuBase child, Vector2 location, bool isSubMenu = true)
|
||||
{
|
||||
// Hide current child
|
||||
HideChild();
|
||||
|
||||
// Set child
|
||||
_childCM = child;
|
||||
_childCM._parentCM = this;
|
||||
_childCM._isSubMenu = isSubMenu;
|
||||
|
||||
// Show child
|
||||
_childCM.Show(this, location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides child popup menu if any opened.
|
||||
/// </summary>
|
||||
public void HideChild()
|
||||
{
|
||||
if (_childCM != null)
|
||||
{
|
||||
_childCM.Hide();
|
||||
_childCM = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the size of the window to match context menu dimensions.
|
||||
/// </summary>
|
||||
protected void UpdateWindowSize()
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
_window.ClientSize = Size * Platform.DpiScale;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when context menu window setup is performed. Can be used to adjust the popup window options.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
protected virtual void OnWindowCreating(ref CreateWindowSettings settings)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on context menu show.
|
||||
/// </summary>
|
||||
protected virtual void OnShow()
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on context menu hide.
|
||||
/// </summary>
|
||||
protected virtual void OnHide()
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
private void OnWindowLostFocus()
|
||||
{
|
||||
// Skip for parent menus (child should handle lost of focus)
|
||||
if (_childCM != null)
|
||||
return;
|
||||
|
||||
// Check if user stopped using that popup menu
|
||||
if (_parentCM != null)
|
||||
{
|
||||
// Focus parent if user clicked over the parent popup
|
||||
var mouse = _parentCM.ScreenToClient(FlaxEngine.Input.MouseScreenPosition / Platform.DpiScale);
|
||||
if (_parentCM.ContainsPoint(ref mouse))
|
||||
{
|
||||
_parentCM._window.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsMouseOver
|
||||
{
|
||||
get
|
||||
{
|
||||
bool result = false;
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
var c = _children[i];
|
||||
if (c.Visible && c.IsMouseOver)
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
// Let root context menu to check if none of the popup windows
|
||||
if (_parentCM == null)
|
||||
{
|
||||
var anyForeground = false;
|
||||
var c = this;
|
||||
while (!anyForeground && c != null)
|
||||
{
|
||||
if (c._window != null && c._window.IsForegroundWindow)
|
||||
anyForeground = true;
|
||||
c = c._childCM;
|
||||
}
|
||||
if (!anyForeground)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
// Draw background
|
||||
var style = Style.Current;
|
||||
var bounds = new Rectangle(Vector2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, style.Background);
|
||||
Render2D.DrawRectangle(bounds, Color.Lerp(style.BackgroundSelected, style.Background, 0.6f));
|
||||
|
||||
base.Draw();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
base.OnMouseDown(location, button);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
base.OnMouseUp(location, button);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Ensure to be hidden
|
||||
Hide();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user