diff --git a/build/PackageDiffIgnore.xml b/build/PackageDiffIgnore.xml
index 875317d81ea1..6f0c700a3375 100644
--- a/build/PackageDiffIgnore.xml
+++ b/build/PackageDiffIgnore.xml
@@ -1982,6 +1982,10 @@
+
+
+
+
diff --git a/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs b/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs
index 41a3a91a7b64..d153d4bf4347 100644
--- a/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs
+++ b/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs
@@ -12,7 +12,7 @@ internal static class DiagnosticViewRegistry
{
internal static EventHandler>? Added;
- private static ImmutableArray _registrations = ImmutableArray.Empty;
+ private static ImmutableArray _registrations = [];
///
/// Gets the list of registered diagnostic providers.
@@ -35,7 +35,9 @@ public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode
}
}
-internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticView View);
+internal sealed record DiagnosticViewRegistration(
+ DiagnosticViewRegistrationMode Mode,
+ IDiagnosticView View);
public enum DiagnosticViewRegistrationMode
{
@@ -43,7 +45,7 @@ public enum DiagnosticViewRegistrationMode
/// Diagnostic is being display on at least one window.
/// I.e. only the main/first opened but move to the next one if the current window is closed.
///
- One,
+ One, // Default
///
/// Diagnostic is being rendered as overlay on each window.
@@ -55,3 +57,18 @@ public enum DiagnosticViewRegistrationMode
///
OnDemand
}
+
+public enum DiagnosticViewRegistrationPosition
+{
+ Normal = 0, // Default
+
+ ///
+ /// Register as the first diagnostic view, ensuring it is displayed first.
+ ///
+ First = -1,
+
+ ///
+ /// Register as the last diagnostic view, ensuring it is displayed last.
+ ///
+ Last = 1,
+}
diff --git a/src/Uno.Foundation/Diagnostics/IDiagnosticView.cs b/src/Uno.Foundation/Diagnostics/IDiagnosticView.cs
index 34847ce89737..2e35bbd3021b 100644
--- a/src/Uno.Foundation/Diagnostics/IDiagnosticView.cs
+++ b/src/Uno.Foundation/Diagnostics/IDiagnosticView.cs
@@ -21,6 +21,8 @@ public interface IDiagnosticView
///
string Name { get; }
+ DiagnosticViewRegistrationPosition Position => DiagnosticViewRegistrationPosition.Normal;
+
///
/// Gets a visual element of the diagnostic, usually a value or an icon.
///
diff --git a/src/Uno.UI.RemoteControl/RemoteControlClient.cs b/src/Uno.UI.RemoteControl/RemoteControlClient.cs
index c05abcabf61a..12f6223f5874 100644
--- a/src/Uno.UI.RemoteControl/RemoteControlClient.cs
+++ b/src/Uno.UI.RemoteControl/RemoteControlClient.cs
@@ -33,7 +33,63 @@ public partial class RemoteControlClient : IRemoteControlClient
public delegate void RemoteControlClientEventEventHandler(object sender, ClientEventEventArgs args);
public delegate void SendMessageFailedEventHandler(object sender, SendMessageFailedEventArgs args);
- public static RemoteControlClient? Instance { get; private set; }
+ public static RemoteControlClient? Instance
+ {
+ get => _instance;
+ private set
+ {
+ _instance = value;
+
+ if (value is { })
+ {
+ while (Interlocked.Exchange(ref _waitingList, null) is { } waitingList)
+ {
+ foreach (var action in waitingList)
+ {
+ action(value);
+ }
+ }
+ }
+ }
+ }
+
+ private static IReadOnlyCollection>? _waitingList;
+
+ ///
+ /// Add a callback to be called when the Instance is available.
+ ///
+ ///
+ /// Will be called synchronously if the instance is already available, no need to check for it before.
+ ///
+ public static void OnRemoteControlClientAvailable(Action action)
+ {
+ if (Instance is { })
+ {
+ action(Instance);
+ }
+ else
+ {
+ // Thread-safe way to add the action to a waiting list for the client to be available
+ while (true)
+ {
+ var waitingList = _waitingList;
+ IReadOnlyCollection> newList = waitingList is null
+ ? [action]
+ : [.. waitingList, action];
+
+ if (Instance is { } i) // Last chance to avoid the waiting list
+ {
+ action(i);
+ break;
+ }
+
+ if (ReferenceEquals(Interlocked.CompareExchange(ref _waitingList, newList, waitingList), waitingList))
+ {
+ break;
+ }
+ }
+ }
+ }
public static RemoteControlClient Initialize(Type appType)
=> Instance = new RemoteControlClient(appType);
@@ -59,6 +115,7 @@ internal static RemoteControlClient Initialize(Type appType, ServerEndpointAttri
private readonly StatusSink _status;
private static readonly TimeSpan _keepAliveInterval = TimeSpan.FromSeconds(30);
+ private static RemoteControlClient? _instance;
private readonly (string endpoint, int port)[]? _serverAddresses;
private readonly Dictionary _processors = new();
private readonly List _preprocessors = new();
@@ -223,7 +280,6 @@ public void RegisterPreProcessor(IRemoteControlPreProcessor preprocessor)
_status.Report(ConnectionState.Connecting);
-
const string lastEndpointKey = "__UNO__" + nameof(RemoteControlClient) + "__last_endpoint";
var preferred = ApplicationData.Current.LocalSettings.Values.TryGetValue(lastEndpointKey, out var lastValue) && lastValue is string lastEp
? _serverAddresses.FirstOrDefault(srv => srv.endpoint.Equals(lastEp, StringComparison.OrdinalIgnoreCase)).endpoint
@@ -429,7 +485,8 @@ private async Task Connect(Uri serverUri, int delay, CancellationTok
{
if (this.Log().IsEnabled(LogLevel.Trace))
{
- this.Log().Trace($"Connecting to [{serverUri}] failed: {e.Message}");
+ var innerMessage = e.InnerException is { } ie ? $" ({ie.Message})" : "";
+ this.Log().Trace($"Connecting to [{serverUri}] failed: {e.Message}{innerMessage}");
}
return new(this, serverUri, watch, null);
diff --git a/src/Uno.UI.RemoteControl/RemoteControlStatus.cs b/src/Uno.UI.RemoteControl/RemoteControlStatus.cs
index 87de6eb8d095..f1915e455f34 100644
--- a/src/Uno.UI.RemoteControl/RemoteControlStatus.cs
+++ b/src/Uno.UI.RemoteControl/RemoteControlStatus.cs
@@ -12,8 +12,27 @@ public record RemoteControlStatus(
ImmutableHashSet MissingRequiredProcessors,
(long Count, ImmutableHashSet Types) InvalidFrames)
{
- public bool IsAllGood => State == ConnectionState.Connected && IsVersionValid == true && MissingRequiredProcessors.IsEmpty && KeepAlive.State == KeepAliveState.Ok && InvalidFrames.Count == 0;
+ ///
+ /// A boolean indicating if everything is fine with the connection and the handshaking succeeded.
+ ///
+ public bool IsAllGood =>
+ State == ConnectionState.Connected
+#if !DEBUG
+ // For debug builds, it's annoying to have the version mismatch preventing the connection
+ // Only Uno devs should get this issue, let's not block them.
+ && IsVersionValid == true
+#endif
+ && MissingRequiredProcessors.IsEmpty
+ && KeepAlive.State == KeepAliveState.Ok
+ && InvalidFrames.Count == 0;
+
+ ///
+ /// If the connection is problematic, meaning that the connection is not in a good state.
+ ///
+ ///
+ /// It's just a negation of .
+ ///
public bool IsProblematic => !IsAllGood;
public (Classification kind, string message) GetSummary()
diff --git a/src/Uno.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs b/src/Uno.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs
index 20ad0d418211..b26d78949ad9 100644
--- a/src/Uno.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs
+++ b/src/Uno.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs
@@ -2,6 +2,7 @@
#if WINUI || HAS_UNO_WINUI
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -316,8 +317,8 @@ public void Show(string viewId)
/// Add a UI diagnostic element to this overlay.
///
/// This will also make this overlay visible (cf. ).
- public void Add(string id, string name, UIElement preview, Func? details = null)
- => Add(new DiagnosticView(id, name, _ => preview, (_, ct) => new(details?.Invoke())));
+ public void Add(string id, string name, UIElement preview, Func? details = null, DiagnosticViewRegistrationPosition position = default)
+ => Add(new DiagnosticView(id, name, _ => preview, (_, ct) => new(details?.Invoke()), position));
///
/// Add a UI diagnostic element to this overlay.
@@ -464,8 +465,8 @@ private void EnqueueUpdate(bool forceUpdate = false)
.Where(ShouldMaterialize)
.Select(reg => reg.View)
.Concat(_localRegistrations)
- .Distinct()
- .ToList();
+ .OrderBy(r => (int)r.Position)
+ .Distinct();
foreach (var view in viewsThatShouldBeMaterialized)
{
diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs b/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs
index d67177d8ce0f..393379f0fcc6 100644
--- a/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs
+++ b/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs
@@ -21,11 +21,16 @@ partial class DiagnosticView
///
/// Type of the control.
/// The user-friendly name of the diagnostics view.
- public static DiagnosticView Register(string friendlyName)
+ /// Defines when the registered diagnostic view should be displayed.
+ /// Defines where the item should be placed in the overlay.
+ public static DiagnosticView Register(
+ string friendlyName,
+ DiagnosticViewRegistrationMode mode = default,
+ DiagnosticViewRegistrationPosition position = default)
where TView : UIElement, new()
{
- var provider = new DiagnosticView(typeof(TView).Name, friendlyName, () => new TView());
- DiagnosticViewRegistry.Register(provider);
+ var provider = new DiagnosticView(typeof(TView).Name, friendlyName, () => new TView(), position: position);
+ DiagnosticViewRegistry.Register(provider, mode);
return provider;
}
@@ -43,10 +48,15 @@ public static DiagnosticView Register(string friendlyName)
/// The user-friendly name of the diagnostics view.
/// Factory to create an instance of the control.
/// Defines when the registered diagnostic view should be displayed.
- public static DiagnosticView Register(string friendlyName, Func factory, DiagnosticViewRegistrationMode mode = default)
+ /// Defines where the item should be placed in the overlay.
+ public static DiagnosticView Register(
+ string friendlyName,
+ Func factory,
+ DiagnosticViewRegistrationMode mode = default,
+ DiagnosticViewRegistrationPosition position = default)
where TView : UIElement
{
- var provider = new DiagnosticView(typeof(TView).Name, friendlyName, factory);
+ var provider = new DiagnosticView(typeof(TView).Name, friendlyName, factory, position: position);
DiagnosticViewRegistry.Register(provider, mode);
return provider;
}
@@ -62,17 +72,21 @@ public static DiagnosticView Register(string friendlyName, FuncThe user-friendly name of the diagnostics view.
/// Delegate to use to update the when the is being updated.
/// Optional delegate used to show more details about the diagnostic info when user taps on the view.
+ /// Defines when the registered diagnostic view should be displayed.
+ /// Defines where the item should be placed in the overlay.
/// A diagnostic view helper class which can be used to push updates of the state (cf. ).
public static DiagnosticView Register(
string friendlyName,
Action update,
- Func? details = null)
+ Func? details = null,
+ DiagnosticViewRegistrationMode mode = default,
+ DiagnosticViewRegistrationPosition position = default)
where TView : FrameworkElement, new()
{
var provider = details is null
- ? new DiagnosticView(typeof(TView).Name, friendlyName, _ => new TView(), update)
- : new DiagnosticView(typeof(TView).Name, friendlyName, _ => new TView(), update, (ctx, state, ct) => new(details(state)));
- DiagnosticViewRegistry.Register(provider);
+ ? new DiagnosticView(typeof(TView).Name, friendlyName, _ => new TView(), update, position: position)
+ : new DiagnosticView(typeof(TView).Name, friendlyName, _ => new TView(), update, (ctx, state, ct) => new(details(state)), position: position);
+ DiagnosticViewRegistry.Register(provider, mode);
return provider;
}
@@ -88,18 +102,22 @@ public static DiagnosticView Register(
/// Factory to create an instance of the generic element.
/// Delegate to use to update the when the is being updated.
/// Optional delegate used to show more details about the diagnostic info when user taps on the view.
+ /// Defines when the registered diagnostic view should be displayed.
+ /// Defines where the item should be placed in the overlay.
/// A diagnostic view helper class which can be used to push updates of the state (cf. ).
public static DiagnosticView Register(
string friendlyName,
Func factory,
Action update,
- Func? details = null)
+ Func? details = null,
+ DiagnosticViewRegistrationMode mode = default,
+ DiagnosticViewRegistrationPosition position = default)
where TView : FrameworkElement
{
var provider = details is null
- ? new DiagnosticView(typeof(TView).Name, friendlyName, factory, update)
- : new DiagnosticView(typeof(TView).Name, friendlyName, factory, update, (ctx, state, ct) => new(details(state)));
- DiagnosticViewRegistry.Register(provider);
+ ? new DiagnosticView(typeof(TView).Name, friendlyName, factory, update, position: position)
+ : new DiagnosticView(typeof(TView).Name, friendlyName, factory, update, (ctx, state, ct) => new(details(state)), position: position);
+ DiagnosticViewRegistry.Register(provider, mode);
return provider;
}
}
diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs b/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs
index a2bcbf6b0851..2c8fbbabcf79 100644
--- a/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs
+++ b/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs
@@ -15,7 +15,8 @@ public class DiagnosticView(
string name,
Func factory,
Action update,
- Func>? details = null)
+ Func>? details = null,
+ DiagnosticViewRegistrationPosition position = default)
: IDiagnosticView
where TView : FrameworkElement
{
@@ -37,6 +38,8 @@ public void Update(TState status)
///
string IDiagnosticView.Name => name;
+ DiagnosticViewRegistrationPosition IDiagnosticView.Position => position;
+
///
object IDiagnosticView.GetElement(IDiagnosticViewContext context)
=> _elementsManager.GetView(context);
diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs b/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs
index 3f0cf8997cb1..619d748bea12 100644
--- a/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs
+++ b/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs
@@ -14,7 +14,8 @@ public class DiagnosticView(
string id,
string name,
Func factory,
- Func>? details = null)
+ Func>? details = null,
+ DiagnosticViewRegistrationPosition position = default)
: IDiagnosticView
where TView : UIElement
{
@@ -22,8 +23,9 @@ public DiagnosticView(
string id,
string name,
Func preview,
- Func>? details = null)
- : this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct))
+ Func>? details = null,
+ DiagnosticViewRegistrationPosition position = default)
+ : this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct), position)
{
}
@@ -33,6 +35,8 @@ public DiagnosticView(
///
string IDiagnosticView.Name => name;
+ DiagnosticViewRegistrationPosition IDiagnosticView.Position => position;
+
///
object IDiagnosticView.GetElement(IDiagnosticViewContext context) => factory(context);
diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.cs b/src/Uno.UI/Diagnostics/DiagnosticView.cs
index 9849ba1e2294..4a7eebf30157 100644
--- a/src/Uno.UI/Diagnostics/DiagnosticView.cs
+++ b/src/Uno.UI/Diagnostics/DiagnosticView.cs
@@ -14,15 +14,17 @@ public partial class DiagnosticView(
string id,
string name,
Func factory,
- Func>? details = null)
- : DiagnosticView(id, name, factory, details)
+ Func>? details = null,
+ DiagnosticViewRegistrationPosition position = default)
+ : DiagnosticView(id, name, factory, details, position)
{
public DiagnosticView(
string id,
string name,
Func preview,
- Func>? details = null)
- : this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct))
+ Func>? details = null,
+ DiagnosticViewRegistrationPosition position = default)
+ : this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct), position)
{
}
}
diff --git a/src/Uno.UI/Diagnostics/DiagnosticViewManager.TView.TState.cs b/src/Uno.UI/Diagnostics/DiagnosticViewManager.TView.TState.cs
index 9598b2262813..ece3e7235dc3 100644
--- a/src/Uno.UI/Diagnostics/DiagnosticViewManager.TView.TState.cs
+++ b/src/Uno.UI/Diagnostics/DiagnosticViewManager.TView.TState.cs
@@ -12,7 +12,9 @@ namespace Uno.Diagnostics.UI;
/// Type of the state used to update the .
/// Factory to create an instance of the .
/// Delegate to use to update the on .
-internal class DiagnosticViewManager(Func factory, Action update)
+internal class DiagnosticViewManager(
+ Func factory,
+ Action update)
where TView : FrameworkElement
{
private event EventHandler? _changed;