// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Diagnostics; using System.Linq; using osuTK; using osuTK.Input; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Events; namespace osu.Framework.Graphics.Cursor { /// /// A container which manages a . /// If a right-click happens on a that implements and exists as a child of the same as this container, /// a will be displayed with bottom-right origin at the right-clicked position. /// public abstract class ContextMenuContainer : CursorEffectContainer { private readonly Menu menu; private IHasContextMenu menuTarget; private Vector2 targetRelativePosition; /// /// Creates a new context menu. Can be overridden to supply custom subclass of . /// protected abstract Menu CreateMenu(); private readonly Container content; protected override Container Content => content; /// /// Creates a new . /// protected ContextMenuContainer() { AddInternal(content = new Container { RelativeSizeAxes = Axes.Both, }); AddInternal(menu = CreateMenu()); } protected override void OnSizingChanged() { base.OnSizingChanged(); if (content != null) { // reset to none to prevent exceptions content.RelativeSizeAxes = Axes.None; content.AutoSizeAxes = Axes.None; // in addition to using this.RelativeSizeAxes, sets RelativeSizeAxes on every axis that is neither relative size nor auto size content.RelativeSizeAxes = Axes.Both & ~AutoSizeAxes; content.AutoSizeAxes = AutoSizeAxes; } } protected override bool OnMouseDown(MouseDownEvent e) { switch (e.Button) { case MouseButton.Right: var (target, items) = FindTargets() .Select(t => (target: t, items: t.ContextMenuItems)) .FirstOrDefault(result => result.items != null); menuTarget = target; if (menuTarget == null || items.Length == 0) { if (menu.State == MenuState.Open) menu.Close(); return false; } menu.Items = items; targetRelativePosition = menuTarget.ToLocalSpace(e.ScreenSpaceMousePosition); menu.Open(); return true; default: cancelDisplay(); return false; } } private void cancelDisplay() { Debug.Assert(menu != null); menu.Close(); menuTarget = null; } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); if (menu.State != MenuState.Open || menuTarget == null) return; if ((menuTarget as Drawable)?.FindClosestParent() != this || (!menuTarget?.IsPresent ?? false)) { cancelDisplay(); return; } Vector2 pos = menuTarget.ToSpaceOfOtherDrawable(targetRelativePosition, this); Vector2 overflow = pos + menu.DrawSize - DrawSize; if (overflow.X > 0) pos.X -= Math.Clamp(overflow.X, 0, menu.DrawWidth); if (overflow.Y > 0) pos.Y -= Math.Clamp(overflow.Y, 0, menu.DrawHeight); if (pos.X < 0) pos.X += Math.Clamp(-pos.X, 0, menu.DrawWidth); if (pos.Y < 0) pos.Y += Math.Clamp(-pos.Y, 0, menu.DrawHeight); menu.Position = pos; } } }