forked from Devolutions/UniGetUI
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSettingsAnchor.cs
More file actions
94 lines (82 loc) · 3.51 KB
/
Copy pathSettingsAnchor.cs
File metadata and controls
94 lines (82 loc) · 3.51 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
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace UniGetUI.Avalonia.Infrastructure;
/// <summary>
/// Scrolls a named control inside a settings sub-page into view and briefly highlights it,
/// so a search result lands the user on the exact setting they typed.
/// </summary>
public static class SettingsAnchor
{
public static void ScrollToAndHighlight(Control root, string anchorName)
{
// The page may still be sliding into the transitioning frame; wait until it's attached
// and laid out so BringIntoView has real bounds to work with.
if (root.IsLoaded)
{
Dispatcher.UIThread.Post(() => Run(root, anchorName), DispatcherPriority.Loaded);
return;
}
void OnLoaded(object? sender, RoutedEventArgs e)
{
root.Loaded -= OnLoaded;
Dispatcher.UIThread.Post(() => Run(root, anchorName), DispatcherPriority.Loaded);
}
root.Loaded += OnLoaded;
}
private static void Run(Control root, string anchorName)
{
var target = root.GetVisualDescendants()
.OfType<Control>()
.FirstOrDefault(c => c.Name == anchorName);
if (target is null) return;
// A SettingsCard's visible surface is an inner Border.settings-card inset 40px per side;
// adorn that so the outline hugs the card rather than the full-width control wrapper.
Control highlightTarget = target.GetVisualDescendants()
.OfType<Border>()
.FirstOrDefault(b => b.Classes.Contains("settings-card"))
?? target;
highlightTarget.BringIntoView();
Highlight(highlightTarget);
}
private static void Highlight(Control target)
{
var layer = AdornerLayer.GetAdornerLayer(target);
if (layer is null) return;
IBrush accent = target.TryFindResource("AccentFillColorDefaultBrush", target.ActualThemeVariant, out var res)
&& res is IBrush brush ? brush : Brushes.DodgerBlue;
var adorner = new Border
{
BorderBrush = accent,
BorderThickness = new Thickness(2),
CornerRadius = target is Border b ? b.CornerRadius : new CornerRadius(8),
IsHitTestVisible = false,
};
AdornerLayer.SetAdornedElement(adorner, target);
layer.Children.Add(adorner);
void Remove() { if (layer.Children.Contains(adorner)) layer.Children.Remove(adorner); }
// A gentle pulse that holds, then fades out and removes itself.
var fade = new Animation
{
Duration = TimeSpan.FromMilliseconds(1800),
Easing = new CubicEaseOut(),
FillMode = FillMode.Forward,
Children =
{
new KeyFrame { Cue = new Cue(0d), Setters = { new Setter(Visual.OpacityProperty, 1d) } },
new KeyFrame { Cue = new Cue(0.55d), Setters = { new Setter(Visual.OpacityProperty, 1d) } },
new KeyFrame { Cue = new Cue(1d), Setters = { new Setter(Visual.OpacityProperty, 0d) } },
},
};
_ = fade.RunAsync(adorner).ContinueWith(
_ => Dispatcher.UIThread.Post(Remove),
TaskScheduler.Default);
}
}