A game framework written with osu! in mind.
at master 195 lines 6.4 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 osu.Framework.Extensions; 6using osu.Framework.Extensions.EnumExtensions; 7using osu.Framework.Graphics.Containers; 8using osu.Framework.Graphics.Shapes; 9using osu.Framework.Input.Events; 10using osuTK; 11using osuTK.Input; 12 13namespace osu.Framework.Graphics.UserInterface 14{ 15 /// <summary> 16 /// A <see cref="Popover"/> is a transient view that appears above other on-screen content. 17 /// It typically is activated by another control and includes an arrow pointing to the location from which it emerged. 18 /// (loosely paraphrasing: https://developer.apple.com/design/human-interface-guidelines/ios/views/popovers/) 19 /// </summary> 20 public abstract class Popover : FocusedOverlayContainer 21 { 22 protected override bool BlockPositionalInput => true; 23 24 public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos) || Arrow.ReceivePositionalInputAt(screenSpacePos); 25 26 public override bool HandleNonPositionalInput => State.Value == Visibility.Visible; 27 28 protected override bool OnKeyDown(KeyDownEvent e) 29 { 30 if (e.Key == Key.Escape) 31 { 32 this.HidePopover(); 33 return true; 34 } 35 36 return base.OnKeyDown(e); 37 } 38 39 /// <summary> 40 /// The <see cref="Anchor"/> that this <see cref="Popover"/> is to be attached to the triggering UI control by. 41 /// </summary> 42 public Anchor PopoverAnchor 43 { 44 get => Anchor; 45 set 46 { 47 BoundingBoxContainer.Origin = value; 48 BoundingBoxContainer.Anchor = value.Opposite(); 49 50 Body.Anchor = Body.Origin = value; 51 Arrow.Anchor = value; 52 Arrow.Rotation = getRotationFor(value); 53 Arrow.Alpha = value == Anchor.Centre ? 0 : 1; 54 AnchorUpdated(value); 55 } 56 } 57 58 /// <summary> 59 /// The container holding all of this popover's elements (the <see cref="Body"/> and the <see cref="Arrow"/>). 60 /// </summary> 61 internal Container BoundingBoxContainer { get; } 62 63 /// <summary> 64 /// The background box of the popover. 65 /// </summary> 66 protected Box Background { get; } 67 68 /// <summary> 69 /// The arrow of this <see cref="Popover"/>, pointing at the component which triggered it. 70 /// </summary> 71 protected internal Drawable Arrow { get; } 72 73 /// <summary> 74 /// The body of this <see cref="Popover"/>, containing the actual contents. 75 /// </summary> 76 protected internal Container Body { get; } 77 78 protected override Container<Drawable> Content { get; } = new Container { AutoSizeAxes = Axes.Both }; 79 80 protected Popover() 81 { 82 base.AddInternal(BoundingBoxContainer = new Container 83 { 84 AutoSizeAxes = Axes.Both, 85 Children = new[] 86 { 87 Arrow = CreateArrow(), 88 Body = new Container 89 { 90 AutoSizeAxes = Axes.Both, 91 Children = new Drawable[] 92 { 93 Background = new Box 94 { 95 RelativeSizeAxes = Axes.Both, 96 }, 97 Content 98 }, 99 } 100 } 101 }); 102 } 103 104 /// <summary> 105 /// Creates an arrow drawable that points away from the given <see cref="Anchor"/>. 106 /// </summary> 107 protected abstract Drawable CreateArrow(); 108 109 protected override void PopIn() => this.FadeIn(); 110 protected override void PopOut() => this.FadeOut(); 111 112 /// <summary> 113 /// Called when <see cref="Anchor"/> is set. 114 /// Can be used to apply custom layout updates to the subcomponents. 115 /// </summary> 116 protected virtual void AnchorUpdated(Anchor anchor) 117 { 118 } 119 120 private float getRotationFor(Anchor anchor) 121 { 122 switch (anchor) 123 { 124 case Anchor.TopLeft: 125 return -45; 126 127 case Anchor.TopCentre: 128 default: 129 return 0; 130 131 case Anchor.TopRight: 132 return 45; 133 134 case Anchor.CentreLeft: 135 return -90; 136 137 case Anchor.CentreRight: 138 return 90; 139 140 case Anchor.BottomLeft: 141 return -135; 142 143 case Anchor.BottomCentre: 144 return -180; 145 146 case Anchor.BottomRight: 147 return 135; 148 } 149 } 150 151 protected internal sealed override void AddInternal(Drawable drawable) => throw new InvalidOperationException($"Use {nameof(Content)} instead."); 152 153 #region Sizing delegation 154 155 // Popovers rely on being 0x0 sized and placed exactly at the attachment point to their drawable for layouting logic. 156 // This can cause undesirable results if somebody tries to directly set the Width/Height of a popover, expecting the body to be resized. 157 // This is done via shadowing rather than overrides, because we still want framework to read the base 0x0 size. 158 159 public new float Width 160 { 161 get => Body.Width; 162 set 163 { 164 if (Body.AutoSizeAxes.HasFlagFast(Axes.X)) 165 Body.AutoSizeAxes &= ~Axes.X; 166 167 Body.Width = value; 168 } 169 } 170 171 public new float Height 172 { 173 get => Body.Height; 174 set 175 { 176 if (Body.AutoSizeAxes.HasFlagFast(Axes.Y)) 177 Body.AutoSizeAxes &= ~Axes.Y; 178 179 Body.Height = value; 180 } 181 } 182 183 public new Vector2 Size 184 { 185 get => Body.Size; 186 set 187 { 188 Width = value.X; 189 Height = value.Y; 190 } 191 } 192 193 #endregion 194 } 195}