Skip to content

Commit

Permalink
feat(diags): Add ability for a diag view to send notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Jun 17, 2024
1 parent 93f0f32 commit 47bfa4d
Show file tree
Hide file tree
Showing 22 changed files with 558 additions and 136 deletions.
29 changes: 29 additions & 0 deletions src/Uno.Foundation/Diagnostics/DiagnosticViewNotification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#nullable enable
using System;
using System.ComponentModel;
using System.Linq;

namespace Uno.Diagnostics.UI;

/// <summary>
/// A notification sent by a diagnostic view to visually show an important status.
/// </summary>
/// <param name="content">The content of the notification.</param>
/// <param name="duration">Configures the duration to wait before automatically hide the notification.</param>
public class DiagnosticViewNotification(object content, TimeSpan? duration = null)
{
[EditorBrowsable(EditorBrowsableState.Never)] // For XAML only
public DiagnosticViewNotification() : this(default!)
{
}

/// <summary>
/// The content of the notification.
/// </summary>
public object Content { get; set; } = content;

/// <summary>
/// Configures the duration to wait before automatically hide the notification.
/// </summary>
public TimeSpan? Duration { get; set; } = duration;
}
10 changes: 5 additions & 5 deletions src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ internal static class DiagnosticViewRegistry
internal static IImmutableList<DiagnosticViewRegistration> Registrations => _registrations;

/// <summary>
/// Register a global diagnostic provider that can be displayed on any window.
/// Register a global diagnostic view that can be displayed on any window.
/// </summary>
/// <param name="provider">A diagnostic provider to display.</param>
public static void Register(IDiagnosticViewProvider provider)
/// <param name="view">A diagnostic view to display.</param>
public static void Register(IDiagnosticView view)
{
ImmutableInterlocked.Update(
ref _registrations,
static (providers, provider) => providers.Add(provider),
new DiagnosticViewRegistration(DiagnosticViewRegistrationMode.One, provider));
new DiagnosticViewRegistration(DiagnosticViewRegistrationMode.One, view));

Added?.Invoke(null, _registrations);
}
}

internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticViewProvider Provider);
internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticView Provider);

internal enum DiagnosticViewRegistrationMode
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ namespace Uno.Diagnostics.UI;
/// <summary>
/// A diagnostic entry that can be displayed by the DiagnosticsOverlay.
/// </summary>
public interface IDiagnosticViewProvider
public interface IDiagnosticView
{
/// <summary>
/// Identifier of the diagnostic view than can be used to request to show or remove it from a DiagnosticsOverlay.
/// </summary>
string Id { get; }

/// <summary>
/// Friendly name of the diagnostic.
/// </summary>
string Name { get; }

/// <summary>
/// Get a preview of the diagnostic, usually a value or an icon.
/// Gets a visual element of the diagnostic, usually a value or an icon.
/// </summary>
/// <param name="context">An update coordinator that can be used to push updates on the preview.</param>
/// <returns>Either a UIElement to be displayed by the diagnostic overlay or a plain object (rendered as text).</returns>
/// <remarks>This is expected to be invoked on the dispatcher used to render the preview.</remarks>
object GetPreview(IDiagnosticViewContext context);
object GetElement(IDiagnosticViewContext context);

/// <summary>
/// Show details of the diagnostic.
Expand Down
2 changes: 2 additions & 0 deletions src/Uno.Foundation/Diagnostics/IDiagnosticViewContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public interface IDiagnosticViewContext
void ScheduleRecurrent(Action action);

void AbortRecurrent(Action action);

void Notify(DiagnosticViewNotification notification);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal record Status(

private class StatusSink
{
private readonly DiagnosticView<HotReloadStatusView, Status> _view = DiagnosticView.Register<HotReloadStatusView, Status>("Hot reload", (view, status) => view.Update(status));
private readonly DiagnosticView<HotReloadStatusView, Status> _view = DiagnosticView.Register<HotReloadStatusView, Status>("Hot reload", ctx => new HotReloadStatusView(ctx), static (view, status) => view.Update(status));

private HotReloadState? _serverState;
private ImmutableDictionary<long, HotReloadServerOperationData> _serverOperations = ImmutableDictionary<long, HotReloadServerOperationData>.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private static void ShowDiagnosticsOnFirstActivation(object snd, WindowActivated
if (snd is Window { RootElement.XamlRoot: { } xamlRoot } window)
{
window.Activated -= ShowDiagnosticsOnFirstActivation;
DiagnosticsOverlay.Get(xamlRoot).IsVisible = true;
DiagnosticsOverlay.Get(xamlRoot).Show();
}
}

Expand Down
97 changes: 86 additions & 11 deletions src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,45 @@
using System;
#nullable enable

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.UI;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using Uno.Diagnostics.UI;
using Uno.UI.RemoteControl.HotReload.Messages;
using static Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor;
using Path = System.IO.Path;

namespace Uno.UI.RemoteControl.HotReload;

[TemplateVisualState(GroupName = "Status", Name = StatusUnknownVisualStateName)]
[TemplateVisualState(GroupName = "Status", Name = StatusErrorVisualStateName)]
[TemplateVisualState(GroupName = "Status", Name = StatusInitializingVisualStateName)]
[TemplateVisualState(GroupName = "Status", Name = StatusIdleVisualStateName)]
[TemplateVisualState(GroupName = "Status", Name = StatusProcessingVisualStateName)]
[TemplateVisualState(GroupName = "Result", Name = ResultNoneVisualStateName)]
[TemplateVisualState(GroupName = "Result", Name = ResultSuccessVisualStateName)]
[TemplateVisualState(GroupName = "Result", Name = ResultFailedVisualStateName)]
internal sealed partial class HotReloadStatusView : Control
{
private const string StatusUnknownVisualStateName = "Unknown";
private const string StatusErrorVisualStateName = "Error";
private const string StatusInitializingVisualStateName = "Initializing";
private const string StatusIdleVisualStateName = "Idle";
private const string StatusProcessingVisualStateName = "Processing";

private const string ResultNoneVisualStateName = "None";
private const string ResultSuccessVisualStateName = "Success";
private const string ResultFailedVisualStateName = "Failed";

#region HeadLine (DP)
public static DependencyProperty HeadLineProperty { get; } = DependencyProperty.Register(
nameof(HeadLine),
Expand Down Expand Up @@ -43,8 +68,40 @@ public ObservableCollection<HotReloadEntryViewModel> History
}
#endregion

public HotReloadStatusView()
#region SuccessNotification (DP)
public static readonly DependencyProperty SuccessNotificationProperty = DependencyProperty.Register(
nameof(SuccessNotification),
typeof(DiagnosticViewNotification),
typeof(HotReloadStatusView),
new PropertyMetadata(default(DiagnosticViewNotification?)));

public DiagnosticViewNotification? SuccessNotification
{
get => (DiagnosticViewNotification?)GetValue(SuccessNotificationProperty);
set => SetValue(SuccessNotificationProperty, value);
}
#endregion

#region FailureNotification (DP)
public static readonly DependencyProperty FailureNotificationProperty = DependencyProperty.Register(
nameof(FailureNotification),
typeof(DiagnosticViewNotification),
typeof(HotReloadStatusView),
new PropertyMetadata(default(DiagnosticViewNotification?)));

public DiagnosticViewNotification? FailureNotification
{
get => (DiagnosticViewNotification?)GetValue(FailureNotificationProperty);
set => SetValue(FailureNotificationProperty, value);
}
#endregion

private readonly IDiagnosticViewContext _ctx;
private string _resultState = "None";

public HotReloadStatusView(IDiagnosticViewContext ctx)
{
_ctx = ctx;
DefaultStyleKey = typeof(HotReloadStatusView);
History = [];

Expand All @@ -57,7 +114,23 @@ public void Update(Status status)
UpdateHeadline(status.State);

VisualStateManager.GoToState(this, GetStatusVisualState(status.State), true);
VisualStateManager.GoToState(this, GetResultVisualState(), true);

var resultState = GetResultVisualState();
if (resultState != _resultState)
{
_resultState = resultState;
VisualStateManager.GoToState(this, resultState, true);
switch (resultState)
{
case ResultSuccessVisualStateName when SuccessNotification is not null:
_ctx.Notify(SuccessNotification);
break;

case ResultFailedVisualStateName when FailureNotification is not null:
_ctx.Notify(FailureNotification);
break;
}
}
}

private void SyncOperations(Status status)
Expand Down Expand Up @@ -156,22 +229,24 @@ State of the hot-reload engine is unknown.
private static string GetStatusVisualState(HotReloadState state)
=> state switch
{
HotReloadState.Disabled => "Disabled",
HotReloadState.Initializing => "Initializing",
HotReloadState.Idle => "Idle",
HotReloadState.Processing => "Processing",
_ => "Unknown"
HotReloadState.Disabled => StatusErrorVisualStateName,
HotReloadState.Initializing => StatusInitializingVisualStateName,
HotReloadState.Idle => StatusIdleVisualStateName,
HotReloadState.Processing => StatusProcessingVisualStateName,
_ => StatusUnknownVisualStateName
};

private string GetResultVisualState()
{
var operations = History;
if (operations is { Count: 0 } || operations.Any(op => !op.IsCompleted))
{
return "None"; // Makes sure to restore to previous None!
return ResultNoneVisualStateName; // Makes sure to restore to previous None!
}

return operations[0].IsSuccess ? "Success" : "Failed";
return operations[0].IsSuccess
? ResultSuccessVisualStateName
: ResultFailedVisualStateName;
}
}

Expand Down
23 changes: 22 additions & 1 deletion src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.RemoteControl.HotReload"
xmlns:diag="using:Uno.Diagnostics.UI"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
Expand All @@ -23,6 +24,26 @@
</DataTemplate>

<Style TargetType="local:HotReloadStatusView">
<Setter Property="SuccessNotification">
<Setter.Value>
<diag:DiagnosticViewNotification Duration="0:0:10">
<diag:DiagnosticViewNotification.Content>
<Ellipse Width="16" Height="16" Fill="Green" />
</diag:DiagnosticViewNotification.Content>
</diag:DiagnosticViewNotification>
</Setter.Value>
</Setter>

<Setter Property="FailureNotification">
<Setter.Value>
<diag:DiagnosticViewNotification Duration="0:0:0">
<diag:DiagnosticViewNotification.Content>
<Ellipse Width="16" Height="16" Fill="Red" />
</diag:DiagnosticViewNotification.Content>
</diag:DiagnosticViewNotification>
</Setter.Value>
</Setter>

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:HotReloadStatusView">
Expand All @@ -34,7 +55,7 @@
<Setter Target="Dot.Fill" Value="Gray" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState x:Name="Error">
<VisualState.Setters>
<Setter Target="Dot.Fill" Value="Red" />
</VisualState.Setters>
Expand Down
10 changes: 6 additions & 4 deletions src/Uno.UI.Toolkit/Diagnostics/DiagnosticCounter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@

namespace Uno.Diagnostics.UI;

internal sealed class DiagnosticCounter(string name, string description) : IDiagnosticViewProvider
internal sealed class DiagnosticCounter(string name, string description) : IDiagnosticView
{
private DiagnosticViewHelper<TextBlock>? _preview;
private DiagnosticViewManager<TextBlock>? _preview;
private long _value;

public string Id => name;

public string Name => name;

public string Description => description;
Expand All @@ -32,10 +34,10 @@ public void Decrement()
_preview?.NotifyChanged();
}

object IDiagnosticViewProvider.GetPreview(IDiagnosticViewContext context)
object IDiagnosticView.GetElement(IDiagnosticViewContext context)
=> (_preview ??= DiagnosticViewHelper.CreateText(() => _value)).GetView(context);

ValueTask<object?> IDiagnosticViewProvider.GetDetailsAsync(IDiagnosticViewContext context, CancellationToken ct)
ValueTask<object?> IDiagnosticView.GetDetailsAsync(IDiagnosticViewContext context, CancellationToken ct)
=> new($"current: {_value}\r\n\r\n{Description}");
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Uno.Diagnostics.UI;

public sealed partial class DiagnosticsOverlay
{
private record DiagnosticElement(DiagnosticsOverlay Overlay, IDiagnosticViewProvider Provider, IDiagnosticViewContext Coordinator) : IDisposable
private record DiagnosticElement(DiagnosticsOverlay Overlay, IDiagnosticView Provider, IDiagnosticViewContext Context) : IDisposable
{
private UIElement? _preview;
private CancellationTokenSource? _details;
Expand All @@ -25,7 +25,7 @@ private UIElement CreatePreview()
{
try
{
var preview = Provider.GetPreview(Coordinator);
var preview = Provider.GetElement(Context);
var element = preview as UIElement ?? DiagnosticViewHelper.CreateText(preview.ToString());

if (ToolTipService.GetToolTip(element) is null)
Expand Down Expand Up @@ -59,9 +59,12 @@ async ValueTask Do()
try
{
var ct = new CancellationTokenSource();
Interlocked.Exchange(ref _details, ct)?.Cancel();
if (Interlocked.Exchange(ref _details, ct) is { IsCancellationRequested: false } previous)
{
await previous.CancelAsync();
}

var details = await Provider.GetDetailsAsync(Coordinator, ct.Token);
var details = await Provider.GetDetailsAsync(Context, ct.Token);
switch (details)
{
case null:
Expand Down
Loading

0 comments on commit 47bfa4d

Please sign in to comment.