Skip to content

Commit df20ef5

Browse files
authored
Merge pull request #18645 from unoplatform/dev/cdb/hr-indicator-ordering
DiagnosticView Ordering
2 parents 272eb55 + 87ac235 commit df20ef5

11 files changed

+162
-33
lines changed

build/PackageDiffIgnore.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,10 @@
19821982
<Member fullName="System.Threading.Tasks.Task`1&lt;System.Boolean&gt; Uno.UI.Helpers.TypeMappings.WaitForResume()" reason="Not really considered a public API, used by internal tooling" />
19831983
<Member fullName="System.Void Uno.UI.Helpers.TypeMappings.Resume(System.Boolean updateLayout)" reason="Not really considered a public API, used by internal tooling" />
19841984
<!-- END TypeMappings -->
1985+
1986+
<!-- BEGIN DiagnosticsOverlay -->
1987+
<Member fullName="System.Void Uno.Diagnostics.UI.DiagnosticsOverlay.Add(System.String id, System.String name, Microsoft.UI.Xaml.UIElement preview, System.Func`1&lt;Microsoft.UI.Xaml.UIElement&gt; details)" reason="Recently public, nobody uses it yet." />
1988+
<!-- END DiagnosticsOverlay -->
19851989
</Methods>
19861990
</IgnoreSet>
19871991

src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal static class DiagnosticViewRegistry
1212
{
1313
internal static EventHandler<IImmutableList<DiagnosticViewRegistration>>? Added;
1414

15-
private static ImmutableArray<DiagnosticViewRegistration> _registrations = ImmutableArray<DiagnosticViewRegistration>.Empty;
15+
private static ImmutableArray<DiagnosticViewRegistration> _registrations = [];
1616

1717
/// <summary>
1818
/// Gets the list of registered diagnostic providers.
@@ -35,15 +35,17 @@ public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode
3535
}
3636
}
3737

38-
internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticView View);
38+
internal sealed record DiagnosticViewRegistration(
39+
DiagnosticViewRegistrationMode Mode,
40+
IDiagnosticView View);
3941

4042
public enum DiagnosticViewRegistrationMode
4143
{
4244
/// <summary>
4345
/// Diagnostic is being display on at least one window.
4446
/// I.e. only the main/first opened but move to the next one if the current window is closed.
4547
/// </summary>
46-
One,
48+
One, // Default
4749

4850
/// <summary>
4951
/// Diagnostic is being rendered as overlay on each window.
@@ -55,3 +57,18 @@ public enum DiagnosticViewRegistrationMode
5557
/// </summary>
5658
OnDemand
5759
}
60+
61+
public enum DiagnosticViewRegistrationPosition
62+
{
63+
Normal = 0, // Default
64+
65+
/// <summary>
66+
/// Register as the first diagnostic view, ensuring it is displayed first.
67+
/// </summary>
68+
First = -1,
69+
70+
/// <summary>
71+
/// Register as the last diagnostic view, ensuring it is displayed last.
72+
/// </summary>
73+
Last = 1,
74+
}

src/Uno.Foundation/Diagnostics/IDiagnosticView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public interface IDiagnosticView
2121
/// </summary>
2222
string Name { get; }
2323

24+
DiagnosticViewRegistrationPosition Position => DiagnosticViewRegistrationPosition.Normal;
25+
2426
/// <summary>
2527
/// Gets a visual element of the diagnostic, usually a value or an icon.
2628
/// </summary>

src/Uno.UI.RemoteControl/RemoteControlClient.cs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,63 @@ public partial class RemoteControlClient : IRemoteControlClient
3333
public delegate void RemoteControlClientEventEventHandler(object sender, ClientEventEventArgs args);
3434
public delegate void SendMessageFailedEventHandler(object sender, SendMessageFailedEventArgs args);
3535

36-
public static RemoteControlClient? Instance { get; private set; }
36+
public static RemoteControlClient? Instance
37+
{
38+
get => _instance;
39+
private set
40+
{
41+
_instance = value;
42+
43+
if (value is { })
44+
{
45+
while (Interlocked.Exchange(ref _waitingList, null) is { } waitingList)
46+
{
47+
foreach (var action in waitingList)
48+
{
49+
action(value);
50+
}
51+
}
52+
}
53+
}
54+
}
55+
56+
private static IReadOnlyCollection<Action<RemoteControlClient>>? _waitingList;
57+
58+
/// <summary>
59+
/// Add a callback to be called when the Instance is available.
60+
/// </summary>
61+
/// <remarks>
62+
/// Will be called synchronously if the instance is already available, no need to check for it before.
63+
/// </remarks>
64+
public static void OnRemoteControlClientAvailable(Action<RemoteControlClient> action)
65+
{
66+
if (Instance is { })
67+
{
68+
action(Instance);
69+
}
70+
else
71+
{
72+
// Thread-safe way to add the action to a waiting list for the client to be available
73+
while (true)
74+
{
75+
var waitingList = _waitingList;
76+
IReadOnlyCollection<Action<RemoteControlClient>> newList = waitingList is null
77+
? [action]
78+
: [.. waitingList, action];
79+
80+
if (Instance is { } i) // Last chance to avoid the waiting list
81+
{
82+
action(i);
83+
break;
84+
}
85+
86+
if (ReferenceEquals(Interlocked.CompareExchange(ref _waitingList, newList, waitingList), waitingList))
87+
{
88+
break;
89+
}
90+
}
91+
}
92+
}
3793

3894
public static RemoteControlClient Initialize(Type appType)
3995
=> Instance = new RemoteControlClient(appType);
@@ -59,6 +115,7 @@ internal static RemoteControlClient Initialize(Type appType, ServerEndpointAttri
59115

60116
private readonly StatusSink _status;
61117
private static readonly TimeSpan _keepAliveInterval = TimeSpan.FromSeconds(30);
118+
private static RemoteControlClient? _instance;
62119
private readonly (string endpoint, int port)[]? _serverAddresses;
63120
private readonly Dictionary<string, IClientProcessor> _processors = new();
64121
private readonly List<IRemoteControlPreProcessor> _preprocessors = new();
@@ -223,7 +280,6 @@ public void RegisterPreProcessor(IRemoteControlPreProcessor preprocessor)
223280

224281
_status.Report(ConnectionState.Connecting);
225282

226-
227283
const string lastEndpointKey = "__UNO__" + nameof(RemoteControlClient) + "__last_endpoint";
228284
var preferred = ApplicationData.Current.LocalSettings.Values.TryGetValue(lastEndpointKey, out var lastValue) && lastValue is string lastEp
229285
? _serverAddresses.FirstOrDefault(srv => srv.endpoint.Equals(lastEp, StringComparison.OrdinalIgnoreCase)).endpoint
@@ -429,7 +485,8 @@ private async Task<Connection> Connect(Uri serverUri, int delay, CancellationTok
429485
{
430486
if (this.Log().IsEnabled(LogLevel.Trace))
431487
{
432-
this.Log().Trace($"Connecting to [{serverUri}] failed: {e.Message}");
488+
var innerMessage = e.InnerException is { } ie ? $" ({ie.Message})" : "";
489+
this.Log().Trace($"Connecting to [{serverUri}] failed: {e.Message}{innerMessage}");
433490
}
434491

435492
return new(this, serverUri, watch, null);

src/Uno.UI.RemoteControl/RemoteControlStatus.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,27 @@ public record RemoteControlStatus(
1212
ImmutableHashSet<RemoteControlStatus.MissingProcessor> MissingRequiredProcessors,
1313
(long Count, ImmutableHashSet<Type> Types) InvalidFrames)
1414
{
15-
public bool IsAllGood => State == ConnectionState.Connected && IsVersionValid == true && MissingRequiredProcessors.IsEmpty && KeepAlive.State == KeepAliveState.Ok && InvalidFrames.Count == 0;
1615

16+
/// <summary>
17+
/// A boolean indicating if everything is fine with the connection and the handshaking succeeded.
18+
/// </summary>
19+
public bool IsAllGood =>
20+
State == ConnectionState.Connected
21+
#if !DEBUG
22+
// For debug builds, it's annoying to have the version mismatch preventing the connection
23+
// Only Uno devs should get this issue, let's not block them.
24+
&& IsVersionValid == true
25+
#endif
26+
&& MissingRequiredProcessors.IsEmpty
27+
&& KeepAlive.State == KeepAliveState.Ok
28+
&& InvalidFrames.Count == 0;
29+
30+
/// <summary>
31+
/// If the connection is problematic, meaning that the connection is not in a good state.
32+
/// </summary>
33+
/// <remarks>
34+
/// It's just a negation of <see cref="IsAllGood"/>.
35+
/// </remarks>
1736
public bool IsProblematic => !IsAllGood;
1837

1938
public (Classification kind, string message) GetSummary()

src/Uno.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#if WINUI || HAS_UNO_WINUI
33
using System;
44
using System.Collections.Generic;
5+
using System.Collections.Immutable;
56
using System.Diagnostics;
67
using System.Linq;
78
using System.Runtime.CompilerServices;
@@ -316,8 +317,8 @@ public void Show(string viewId)
316317
/// Add a UI diagnostic element to this overlay.
317318
/// </summary>
318319
/// <remarks>This will also make this overlay visible (cf. <see cref="Show(bool?)"/>).</remarks>
319-
public void Add(string id, string name, UIElement preview, Func<UIElement>? details = null)
320-
=> Add(new DiagnosticView(id, name, _ => preview, (_, ct) => new(details?.Invoke())));
320+
public void Add(string id, string name, UIElement preview, Func<UIElement>? details = null, DiagnosticViewRegistrationPosition position = default)
321+
=> Add(new DiagnosticView(id, name, _ => preview, (_, ct) => new(details?.Invoke()), position));
321322

322323
/// <summary>
323324
/// Add a UI diagnostic element to this overlay.
@@ -464,8 +465,8 @@ private void EnqueueUpdate(bool forceUpdate = false)
464465
.Where(ShouldMaterialize)
465466
.Select(reg => reg.View)
466467
.Concat(_localRegistrations)
467-
.Distinct()
468-
.ToList();
468+
.OrderBy(r => (int)r.Position)
469+
.Distinct();
469470

470471
foreach (var view in viewsThatShouldBeMaterialized)
471472
{

src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ partial class DiagnosticView
2121
/// </remarks>
2222
/// <typeparam name="TView">Type of the control.</typeparam>
2323
/// <param name="friendlyName">The user-friendly name of the diagnostics view.</param>
24-
public static DiagnosticView<TView> Register<TView>(string friendlyName)
24+
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
25+
/// <param name="position">Defines where the item should be placed in the overlay.</param>
26+
public static DiagnosticView<TView> Register<TView>(
27+
string friendlyName,
28+
DiagnosticViewRegistrationMode mode = default,
29+
DiagnosticViewRegistrationPosition position = default)
2530
where TView : UIElement, new()
2631
{
27-
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, () => new TView());
28-
DiagnosticViewRegistry.Register(provider);
32+
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, () => new TView(), position: position);
33+
DiagnosticViewRegistry.Register(provider, mode);
2934
return provider;
3035
}
3136

@@ -43,10 +48,15 @@ public static DiagnosticView<TView> Register<TView>(string friendlyName)
4348
/// <param name="friendlyName">The user-friendly name of the diagnostics view.</param>
4449
/// <param name="factory">Factory to create an instance of the control.</param>
4550
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
46-
public static DiagnosticView<TView> Register<TView>(string friendlyName, Func<TView> factory, DiagnosticViewRegistrationMode mode = default)
51+
/// <param name="position">Defines where the item should be placed in the overlay.</param>
52+
public static DiagnosticView<TView> Register<TView>(
53+
string friendlyName,
54+
Func<TView> factory,
55+
DiagnosticViewRegistrationMode mode = default,
56+
DiagnosticViewRegistrationPosition position = default)
4757
where TView : UIElement
4858
{
49-
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, factory);
59+
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, factory, position: position);
5060
DiagnosticViewRegistry.Register(provider, mode);
5161
return provider;
5262
}
@@ -62,17 +72,21 @@ public static DiagnosticView<TView> Register<TView>(string friendlyName, Func<TV
6272
/// <param name="friendlyName">The user-friendly name of the diagnostics view.</param>
6373
/// <param name="update">Delegate to use to update the <typeparamref name="TView"/> when the <typeparamref name="TState"/> is being updated.</param>
6474
/// <param name="details">Optional delegate used to show more details about the diagnostic info when user taps on the view.</param>
75+
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
76+
/// <param name="position">Defines where the item should be placed in the overlay.</param>
6577
/// <returns>A diagnostic view helper class which can be used to push updates of the state (cf. <see cref="DiagnosticView{TView,TState}.Update"/>).</returns>
6678
public static DiagnosticView<TView, TState> Register<TView, TState>(
6779
string friendlyName,
6880
Action<TView, TState> update,
69-
Func<TState, object?>? details = null)
81+
Func<TState, object?>? details = null,
82+
DiagnosticViewRegistrationMode mode = default,
83+
DiagnosticViewRegistrationPosition position = default)
7084
where TView : FrameworkElement, new()
7185
{
7286
var provider = details is null
73-
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update)
74-
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update, (ctx, state, ct) => new(details(state)));
75-
DiagnosticViewRegistry.Register(provider);
87+
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update, position: position)
88+
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update, (ctx, state, ct) => new(details(state)), position: position);
89+
DiagnosticViewRegistry.Register(provider, mode);
7690
return provider;
7791
}
7892

@@ -88,18 +102,22 @@ public static DiagnosticView<TView, TState> Register<TView, TState>(
88102
/// <param name="factory">Factory to create an instance of the generic element.</param>
89103
/// <param name="update">Delegate to use to update the <typeparamref name="TView"/> when the <typeparamref name="TState"/> is being updated.</param>
90104
/// <param name="details">Optional delegate used to show more details about the diagnostic info when user taps on the view.</param>
105+
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
106+
/// <param name="position">Defines where the item should be placed in the overlay.</param>
91107
/// <returns>A diagnostic view helper class which can be used to push updates of the state (cf. <see cref="DiagnosticView{TView,TState}.Update"/>).</returns>
92108
public static DiagnosticView<TView, TState> Register<TView, TState>(
93109
string friendlyName,
94110
Func<IDiagnosticViewContext, TView> factory,
95111
Action<TView, TState> update,
96-
Func<TState, object?>? details = null)
112+
Func<TState, object?>? details = null,
113+
DiagnosticViewRegistrationMode mode = default,
114+
DiagnosticViewRegistrationPosition position = default)
97115
where TView : FrameworkElement
98116
{
99117
var provider = details is null
100-
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update)
101-
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update, (ctx, state, ct) => new(details(state)));
102-
DiagnosticViewRegistry.Register(provider);
118+
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update, position: position)
119+
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update, (ctx, state, ct) => new(details(state)), position: position);
120+
DiagnosticViewRegistry.Register(provider, mode);
103121
return provider;
104122
}
105123
}

src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public class DiagnosticView<TView, TState>(
1515
string name,
1616
Func<IDiagnosticViewContext, TView> factory,
1717
Action<TView, TState> update,
18-
Func<IDiagnosticViewContext, TState, CancellationToken, ValueTask<object?>>? details = null)
18+
Func<IDiagnosticViewContext, TState, CancellationToken, ValueTask<object?>>? details = null,
19+
DiagnosticViewRegistrationPosition position = default)
1920
: IDiagnosticView
2021
where TView : FrameworkElement
2122
{
@@ -37,6 +38,8 @@ public void Update(TState status)
3738
/// <inheritdoc />
3839
string IDiagnosticView.Name => name;
3940

41+
DiagnosticViewRegistrationPosition IDiagnosticView.Position => position;
42+
4043
/// <inheritdoc />
4144
object IDiagnosticView.GetElement(IDiagnosticViewContext context)
4245
=> _elementsManager.GetView(context);

src/Uno.UI/Diagnostics/DiagnosticView.TView.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ public class DiagnosticView<TView>(
1414
string id,
1515
string name,
1616
Func<IDiagnosticViewContext, TView> factory,
17-
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null)
17+
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null,
18+
DiagnosticViewRegistrationPosition position = default)
1819
: IDiagnosticView
1920
where TView : UIElement
2021
{
2122
public DiagnosticView(
2223
string id,
2324
string name,
2425
Func<TView> preview,
25-
Func<CancellationToken, ValueTask<object?>>? details = null)
26-
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct))
26+
Func<CancellationToken, ValueTask<object?>>? details = null,
27+
DiagnosticViewRegistrationPosition position = default)
28+
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct), position)
2729
{
2830
}
2931

@@ -33,6 +35,8 @@ public DiagnosticView(
3335
/// <inheritdoc />
3436
string IDiagnosticView.Name => name;
3537

38+
DiagnosticViewRegistrationPosition IDiagnosticView.Position => position;
39+
3640
/// <inheritdoc />
3741
object IDiagnosticView.GetElement(IDiagnosticViewContext context) => factory(context);
3842

src/Uno.UI/Diagnostics/DiagnosticView.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ public partial class DiagnosticView(
1414
string id,
1515
string name,
1616
Func<IDiagnosticViewContext, UIElement> factory,
17-
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null)
18-
: DiagnosticView<UIElement>(id, name, factory, details)
17+
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null,
18+
DiagnosticViewRegistrationPosition position = default)
19+
: DiagnosticView<UIElement>(id, name, factory, details, position)
1920
{
2021
public DiagnosticView(
2122
string id,
2223
string name,
2324
Func<UIElement> preview,
24-
Func<CancellationToken, ValueTask<object?>>? details = null)
25-
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct))
25+
Func<CancellationToken, ValueTask<object?>>? details = null,
26+
DiagnosticViewRegistrationPosition position = default)
27+
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct), position)
2628
{
2729
}
2830
}

src/Uno.UI/Diagnostics/DiagnosticViewManager.TView.TState.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ namespace Uno.Diagnostics.UI;
1212
/// <typeparam name="TState">Type of the state used to update the <typeparamref name="TView"/>.</typeparam>
1313
/// <param name="factory">Factory to create an instance of the <typeparamref name="TView"/>.</param>
1414
/// <param name="update">Delegate to use to update the <typeparamref name="TView"/> on <see cref="NotifyChanged"/>.</param>
15-
internal class DiagnosticViewManager<TView, TState>(Func<IDiagnosticViewContext, TView> factory, Action<IDiagnosticViewContext, TView, TState> update)
15+
internal class DiagnosticViewManager<TView, TState>(
16+
Func<IDiagnosticViewContext, TView> factory,
17+
Action<IDiagnosticViewContext, TView, TState> update)
1618
where TView : FrameworkElement
1719
{
1820
private event EventHandler<TState>? _changed;

0 commit comments

Comments
 (0)