A game framework written with osu! in mind.
at master 134 lines 4.5 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Diagnostics; 6using System.Linq; 7using osuTK; 8using osuTK.Input; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.UserInterface; 11using osu.Framework.Input; 12using osu.Framework.Input.Events; 13 14namespace osu.Framework.Graphics.Cursor 15{ 16 /// <summary> 17 /// A container which manages a <see cref="Menu"/>. 18 /// If a right-click happens on a <see cref="Drawable"/> that implements <see cref="IHasContextMenu"/> and exists as a child of the same <see cref="InputManager"/> as this container, 19 /// a <see cref="Menu"/> will be displayed with bottom-right origin at the right-clicked position. 20 /// </summary> 21 public abstract class ContextMenuContainer : CursorEffectContainer<ContextMenuContainer, IHasContextMenu> 22 { 23 private readonly Menu menu; 24 25 private IHasContextMenu menuTarget; 26 private Vector2 targetRelativePosition; 27 28 /// <summary> 29 /// Creates a new context menu. Can be overridden to supply custom subclass of <see cref="Menu"/>. 30 /// </summary> 31 protected abstract Menu CreateMenu(); 32 33 private readonly Container content; 34 35 protected override Container<Drawable> Content => content; 36 37 /// <summary> 38 /// Creates a new <see cref="ContextMenuContainer"/>. 39 /// </summary> 40 protected ContextMenuContainer() 41 { 42 AddInternal(content = new Container 43 { 44 RelativeSizeAxes = Axes.Both, 45 }); 46 47 AddInternal(menu = CreateMenu()); 48 } 49 50 protected override void OnSizingChanged() 51 { 52 base.OnSizingChanged(); 53 54 if (content != null) 55 { 56 // reset to none to prevent exceptions 57 content.RelativeSizeAxes = Axes.None; 58 content.AutoSizeAxes = Axes.None; 59 60 // in addition to using this.RelativeSizeAxes, sets RelativeSizeAxes on every axis that is neither relative size nor auto size 61 content.RelativeSizeAxes = Axes.Both & ~AutoSizeAxes; 62 content.AutoSizeAxes = AutoSizeAxes; 63 } 64 } 65 66 protected override bool OnMouseDown(MouseDownEvent e) 67 { 68 switch (e.Button) 69 { 70 case MouseButton.Right: 71 var (target, items) = FindTargets() 72 .Select(t => (target: t, items: t.ContextMenuItems)) 73 .FirstOrDefault(result => result.items != null); 74 75 menuTarget = target; 76 77 if (menuTarget == null || items.Length == 0) 78 { 79 if (menu.State == MenuState.Open) 80 menu.Close(); 81 return false; 82 } 83 84 menu.Items = items; 85 86 targetRelativePosition = menuTarget.ToLocalSpace(e.ScreenSpaceMousePosition); 87 88 menu.Open(); 89 return true; 90 91 default: 92 cancelDisplay(); 93 return false; 94 } 95 } 96 97 private void cancelDisplay() 98 { 99 Debug.Assert(menu != null); 100 101 menu.Close(); 102 menuTarget = null; 103 } 104 105 protected override void UpdateAfterChildren() 106 { 107 base.UpdateAfterChildren(); 108 109 if (menu.State != MenuState.Open || menuTarget == null) return; 110 111 if ((menuTarget as Drawable)?.FindClosestParent<ContextMenuContainer>() != this || (!menuTarget?.IsPresent ?? false)) 112 { 113 cancelDisplay(); 114 return; 115 } 116 117 Vector2 pos = menuTarget.ToSpaceOfOtherDrawable(targetRelativePosition, this); 118 119 Vector2 overflow = pos + menu.DrawSize - DrawSize; 120 121 if (overflow.X > 0) 122 pos.X -= Math.Clamp(overflow.X, 0, menu.DrawWidth); 123 if (overflow.Y > 0) 124 pos.Y -= Math.Clamp(overflow.Y, 0, menu.DrawHeight); 125 126 if (pos.X < 0) 127 pos.X += Math.Clamp(-pos.X, 0, menu.DrawWidth); 128 if (pos.Y < 0) 129 pos.Y += Math.Clamp(-pos.Y, 0, menu.DrawHeight); 130 131 menu.Position = pos; 132 } 133 } 134}