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