diff --git a/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs b/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs index 41a3a91a7b64..f0e22250f3a0 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. @@ -24,18 +24,38 @@ internal static class DiagnosticViewRegistry /// /// A diagnostic view to display. /// Defines when the registered diagnostic view should be displayed. - public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode mode = default) + public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode mode = default, DiagnosticViewRegistrationPosition position = default) { ImmutableInterlocked.Update( ref _registrations, static (providers, provider) => providers.Add(provider), - new DiagnosticViewRegistration(mode, view)); + new DiagnosticViewRegistration(mode, position, view)); Added?.Invoke(null, _registrations); } } -internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticView View); +internal sealed record DiagnosticViewRegistration( + DiagnosticViewRegistrationMode Mode, + DiagnosticViewRegistrationPosition Position, + IDiagnosticView View) : IComparable +{ + public int CompareTo(DiagnosticViewRegistration? other) + { + if (other is null) + { + return 1; + } + + if (Position == other.Position) + { + // If the position is the same, we compare the view id to ensure a stable order. + return string.Compare(View.Id, other.View.Id, StringComparison.Ordinal); + } + + return (int)Position - (int)other.Position; + } +} public enum DiagnosticViewRegistrationMode { @@ -43,7 +63,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 +75,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.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs b/src/Uno.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs index 20ad0d418211..f5aafa64ffaf 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; @@ -462,10 +463,10 @@ private void EnqueueUpdate(bool forceUpdate = false) var viewsThatShouldBeMaterialized = DiagnosticViewRegistry .Registrations .Where(ShouldMaterialize) + .Order() // See DiagnosticViewRegistration.CompareTo .Select(reg => reg.View) - .Concat(_localRegistrations) - .Distinct() - .ToList(); + .Concat(_localRegistrations) // They are at the end of the list. + .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..2ec8b905a3bb 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); + DiagnosticViewRegistry.Register(provider, mode, position); return provider; } @@ -43,11 +48,16 @@ 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); - DiagnosticViewRegistry.Register(provider, mode); + DiagnosticViewRegistry.Register(provider, mode, position); 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); + DiagnosticViewRegistry.Register(provider, mode, position); 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); + DiagnosticViewRegistry.Register(provider, mode, position); return provider; } }