-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Expand file tree
/
Copy pathPopup.cs
More file actions
471 lines (409 loc) · 16 KB
/
Copy pathPopup.cs
File metadata and controls
471 lines (409 loc) · 16 KB
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Displays a popup window.
/// </summary>
public class Popup : Control, IVisualTreeHost
{
/// <summary>
/// Defines the <see cref="Child"/> property.
/// </summary>
public static readonly StyledProperty<Control> ChildProperty =
AvaloniaProperty.Register<Popup, Control>(nameof(Child));
/// <summary>
/// Defines the <see cref="IsOpen"/> property.
/// </summary>
public static readonly DirectProperty<Popup, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<Popup, bool>(
nameof(IsOpen),
o => o.IsOpen,
(o, v) => o.IsOpen = v);
/// <summary>
/// Defines the <see cref="PlacementMode"/> property.
/// </summary>
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> HorizontalOffsetProperty =
AvaloniaProperty.Register<Popup, double>(nameof(HorizontalOffset));
/// <summary>
/// Defines the <see cref="VerticalOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> VerticalOffsetProperty =
AvaloniaProperty.Register<Popup, double>(nameof(VerticalOffset));
/// <summary>
/// Defines the <see cref="PlacementTarget"/> property.
/// </summary>
public static readonly StyledProperty<Control> PlacementTargetProperty =
AvaloniaProperty.Register<Popup, Control>(nameof(PlacementTarget));
/// <summary>
/// Defines the <see cref="StaysOpen"/> property.
/// </summary>
public static readonly StyledProperty<bool> StaysOpenProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(StaysOpen), true);
/// <summary>
/// Defines the <see cref="Topmost"/> property.
/// </summary>
public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpen;
private PopupRoot _popupRoot;
private TopLevel _topLevel;
private IDisposable _nonClientListener;
bool _ignoreIsOpenChanged = false;
/// <summary>
/// Initializes static members of the <see cref="Popup"/> class.
/// </summary>
static Popup()
{
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged);
TopmostProperty.Changed.AddClassHandler<Popup>((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
}
/// <summary>
/// Raised when the popup closes.
/// </summary>
public event EventHandler Closed;
/// <summary>
/// Raised when the popup opens.
/// </summary>
public event EventHandler Opened;
/// <summary>
/// Raised when the popup root has been created, but before it has been shown.
/// </summary>
public event EventHandler PopupRootCreated;
/// <summary>
/// Gets or sets the control to display in the popup.
/// </summary>
[Content]
public Control Child
{
get { return GetValue(ChildProperty); }
set { SetValue(ChildProperty, value); }
}
/// <summary>
/// Gets or sets a dependency resolver for the <see cref="PopupRoot"/>.
/// </summary>
/// <remarks>
/// This property allows a client to customize the behaviour of the popup by injecting
/// a specialized dependency resolver into the <see cref="PopupRoot"/>'s constructor.
/// </remarks>
public IAvaloniaDependencyResolver DependencyResolver
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether the popup is currently open.
/// </summary>
public bool IsOpen
{
get { return _isOpen; }
set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
}
/// <summary>
/// Gets or sets the placement mode of the popup in relation to the <see cref="PlacementTarget"/>.
/// </summary>
public PlacementMode PlacementMode
{
get { return GetValue(PlacementModeProperty); }
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
/// when its opened at a position where it would otherwise overlap the screen edge.
/// </summary>
public bool ObeyScreenEdges
{
get => GetValue(ObeyScreenEdgesProperty);
set => SetValue(ObeyScreenEdgesProperty, value);
}
/// <summary>
/// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
public double HorizontalOffset
{
get { return GetValue(HorizontalOffsetProperty); }
set { SetValue(HorizontalOffsetProperty, value); }
}
/// <summary>
/// Gets or sets the Vertical offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
public double VerticalOffset
{
get { return GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
/// <summary>
/// Gets or sets the control that is used to determine the popup's position.
/// </summary>
public Control PlacementTarget
{
get { return GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); }
}
/// <summary>
/// Gets the root of the popup window.
/// </summary>
public PopupRoot PopupRoot => _popupRoot;
/// <summary>
/// Gets or sets a value indicating whether the popup should stay open when the popup is
/// pressed or loses focus.
/// </summary>
public bool StaysOpen
{
get { return GetValue(StaysOpenProperty); }
set { SetValue(StaysOpenProperty, value); }
}
/// <summary>
/// Gets or sets whether this popup appears on top of all other windows
/// </summary>
public bool Topmost
{
get { return GetValue(TopmostProperty); }
set { SetValue(TopmostProperty, value); }
}
/// <summary>
/// Gets the root of the popup window.
/// </summary>
IVisual IVisualTreeHost.Root => _popupRoot;
/// <summary>
/// Opens the popup.
/// </summary>
public void Open()
{
if (_popupRoot == null)
{
_popupRoot = new PopupRoot(DependencyResolver)
{
[~ContentControl.ContentProperty] = this[~ChildProperty],
[~WidthProperty] = this[~WidthProperty],
[~HeightProperty] = this[~HeightProperty],
[~MinWidthProperty] = this[~MinWidthProperty],
[~MaxWidthProperty] = this[~MaxWidthProperty],
[~MinHeightProperty] = this[~MinHeightProperty],
[~MaxHeightProperty] = this[~MaxHeightProperty],
};
((ISetLogicalParent)_popupRoot).SetParent(this);
}
_popupRoot.Position = GetPosition();
if (_topLevel == null && PlacementTarget != null)
{
_topLevel = PlacementTarget.GetSelfAndLogicalAncestors().First(x => x is TopLevel) as TopLevel;
}
if (_topLevel != null)
{
var window = _topLevel as Window;
if (window != null)
{
window.Deactivated += WindowDeactivated;
}
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot != null && parentPopuproot.Parent != null)
{
((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
}
}
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick);
}
PopupRootCreated?.Invoke(this, EventArgs.Empty);
_popupRoot.Show();
if (ObeyScreenEdges)
{
_popupRoot.SnapInsideScreenEdges();
}
_ignoreIsOpenChanged = true;
IsOpen = true;
_ignoreIsOpenChanged = false;
Opened?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Closes the popup.
/// </summary>
public void Close()
{
if (_popupRoot != null)
{
if (_topLevel != null)
{
_topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
var window = _topLevel as Window;
if (window != null)
window.Deactivated -= WindowDeactivated;
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot != null && parentPopuproot.Parent != null)
{
((Popup)parentPopuproot.Parent).Closed -= ParentClosed;
}
}
_nonClientListener?.Dispose();
_nonClientListener = null;
}
_popupRoot.Hide();
}
IsOpen = false;
Closed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Measures the control.
/// </summary>
/// <param name="availableSize">The available size for the control.</param>
/// <returns>A size of 0,0 as Popup itself takes up no space.</returns>
protected override Size MeasureCore(Size availableSize)
{
return new Size();
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
_topLevel = e.Root as TopLevel;
}
/// <inheritdoc/>
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_topLevel = null;
if (_popupRoot != null)
{
((ISetLogicalParent)_popupRoot).SetParent(null);
_popupRoot.Dispose();
_popupRoot = null;
}
}
/// <summary>
/// Called when the <see cref="IsOpen"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void IsOpenChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_ignoreIsOpenChanged)
{
if ((bool)e.NewValue)
{
Open();
}
else
{
Close();
}
}
}
/// <summary>
/// Called when the <see cref="Child"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
{
LogicalChildren.Clear();
((ISetLogicalParent)e.OldValue)?.SetParent(null);
if (e.NewValue != null)
{
((ISetLogicalParent)e.NewValue).SetParent(this);
LogicalChildren.Add((ILogical)e.NewValue);
}
}
/// <summary>
/// Gets the position for the popup based on the placement properties.
/// </summary>
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual PixelPoint GetPosition()
{
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
return result;
}
internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
var root = target?.GetVisualRoot();
var mode = root != null ? placement : PlacementMode.Pointer;
var scaling = root?.RenderScaling ?? 1;
switch (mode)
{
case PlacementMode.Pointer:
if (popupRoot != null)
{
var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling);
var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default;
return new PixelPoint(
screenOffset.X + mouseOffset.X,
screenOffset.Y + mouseOffset.Y);
}
return default;
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default;
case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
}
}
private void ListenForNonClientClick(RawInputEventArgs e)
{
var mouse = e as RawMouseEventArgs;
if (!StaysOpen && mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
{
Close();
}
}
private void PointerPressedOutside(object sender, PointerPressedEventArgs e)
{
if (!StaysOpen)
{
if (!IsChildOrThis((IVisual)e.Source))
{
Close();
e.Handled = true;
}
}
}
private bool IsChildOrThis(IVisual child)
{
IVisual root = child.GetVisualRoot();
while (root is PopupRoot)
{
if (root == PopupRoot) return true;
root = ((PopupRoot)root).Parent.GetVisualRoot();
}
return false;
}
private void WindowDeactivated(object sender, EventArgs e)
{
if (!StaysOpen)
{
Close();
}
}
private void ParentClosed(object sender, EventArgs e)
{
if (!StaysOpen)
{
Close();
}
}
}
}