Files
LanMountainDesktop/LanMountainDesktop.SamplePlugin/SamplePluginStatusClockWidget.cs

285 lines
10 KiB
C#
Raw Normal View History

2026-03-09 12:27:33 +08:00
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.PluginSdk;
namespace LanMountainDesktop.SamplePlugin;
internal sealed class SamplePluginStatusClockWidget : Border
{
private readonly PluginDesktopComponentContext _context;
2026-03-09 14:14:50 +08:00
private readonly SamplePluginRuntimeStateService _stateService;
private readonly SamplePluginClockService _clockService;
private readonly IPluginMessageBus _messageBus;
2026-03-09 12:27:33 +08:00
private readonly TextBlock _timeTextBlock;
2026-03-09 14:14:50 +08:00
private readonly TextBlock _subtitleTextBlock;
2026-03-09 12:27:33 +08:00
private readonly StackPanel _statusPanel;
2026-03-09 14:14:50 +08:00
private readonly Border _statusHost;
private readonly List<IDisposable> _subscriptions = [];
private string? _instanceId;
2026-03-09 12:27:33 +08:00
public SamplePluginStatusClockWidget(PluginDesktopComponentContext context)
{
_context = context;
2026-03-09 14:14:50 +08:00
_stateService = context.GetService<SamplePluginRuntimeStateService>()
?? throw new InvalidOperationException("SamplePluginRuntimeStateService is not available.");
_clockService = context.GetService<SamplePluginClockService>()
?? throw new InvalidOperationException("SamplePluginClockService is not available.");
_messageBus = context.GetService<IPluginMessageBus>()
?? throw new InvalidOperationException("IPluginMessageBus is not available.");
2026-03-09 12:27:33 +08:00
_timeTextBlock = new TextBlock
{
Foreground = Brushes.White,
FontWeight = FontWeight.Bold,
HorizontalAlignment = HorizontalAlignment.Left
};
2026-03-09 14:14:50 +08:00
_subtitleTextBlock = new TextBlock
2026-03-09 12:27:33 +08:00
{
Foreground = new SolidColorBrush(Color.Parse("#FFBFE9FF")),
2026-03-09 14:14:50 +08:00
HorizontalAlignment = HorizontalAlignment.Left,
TextWrapping = TextWrapping.Wrap
2026-03-09 12:27:33 +08:00
};
_statusPanel = new StackPanel
{
Spacing = 8
};
2026-03-09 14:14:50 +08:00
_statusHost = new Border
{
Background = new SolidColorBrush(Color.Parse("#1F082F49")),
BorderBrush = new SolidColorBrush(Color.Parse("#5538BDF8")),
BorderThickness = new Thickness(1),
Child = _statusPanel
};
2026-03-09 12:27:33 +08:00
Background = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops =
[
new GradientStop(Color.Parse("#FF07111F"), 0),
new GradientStop(Color.Parse("#FF0C4A6E"), 0.55),
new GradientStop(Color.Parse("#FF0EA5E9"), 1)
]
};
BorderBrush = new SolidColorBrush(Color.Parse("#6648C7FF"));
BorderThickness = new Thickness(1);
HorizontalAlignment = HorizontalAlignment.Stretch;
VerticalAlignment = VerticalAlignment.Stretch;
Child = new Grid
{
RowDefinitions = new RowDefinitions("Auto,*"),
RowSpacing = 14,
Children =
{
new StackPanel
{
Spacing = 4,
HorizontalAlignment = HorizontalAlignment.Left,
Children =
{
_timeTextBlock,
2026-03-09 14:14:50 +08:00
_subtitleTextBlock
2026-03-09 12:27:33 +08:00
}
},
2026-03-09 14:14:50 +08:00
_statusHost
2026-03-09 12:27:33 +08:00
}
};
Grid.SetRow(((Grid)Child).Children[1], 1);
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
2026-03-09 14:14:50 +08:00
RefreshClock(_clockService.CurrentTime);
UpdateSubtitle();
2026-03-09 12:27:33 +08:00
RefreshStatusPanel();
ApplyScale();
}
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
2026-03-09 14:14:50 +08:00
if (string.IsNullOrWhiteSpace(_instanceId))
{
_instanceId = _stateService.RegisterComponentInstance(
_context.ComponentId,
_context.PlacementId,
_context.CellSize);
}
_stateService.MarkFrontendReady("Widget surface is connected to plugin services and communication.");
SubscribeToPluginBus();
RefreshClock(_clockService.CurrentTime);
UpdateSubtitle();
2026-03-09 12:27:33 +08:00
RefreshStatusPanel();
}
private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
2026-03-09 14:14:50 +08:00
foreach (var subscription in _subscriptions)
{
subscription.Dispose();
}
2026-03-09 12:27:33 +08:00
2026-03-09 14:14:50 +08:00
_subscriptions.Clear();
if (string.IsNullOrWhiteSpace(_instanceId))
{
return;
}
_stateService.UnregisterComponentInstance(_instanceId);
_instanceId = null;
2026-03-09 12:27:33 +08:00
}
private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
{
ApplyScale();
RefreshStatusPanel();
}
2026-03-09 14:14:50 +08:00
private void SubscribeToPluginBus()
{
if (_subscriptions.Count > 0)
{
return;
}
_subscriptions.Add(_messageBus.Subscribe<SamplePluginClockTickMessage>(message =>
Dispatcher.UIThread.Post(() => RefreshClock(message.CurrentTime))));
_subscriptions.Add(_messageBus.Subscribe<SamplePluginStateChangedMessage>(_ =>
Dispatcher.UIThread.Post(() =>
{
UpdateSubtitle();
RefreshStatusPanel();
})));
}
private void RefreshClock(DateTimeOffset currentTime)
2026-03-09 12:27:33 +08:00
{
2026-03-09 14:14:50 +08:00
_timeTextBlock.Text = currentTime.LocalDateTime.ToString("HH:mm:ss");
}
private void UpdateSubtitle()
{
var snapshot = _stateService.GetSnapshot();
_subtitleTextBlock.Text = string.IsNullOrWhiteSpace(_context.PlacementId)
? $"Preview surface | placed: {snapshot.PlacedCount}"
: $"Placement {_context.PlacementId} | placed: {snapshot.PlacedCount}";
2026-03-09 12:27:33 +08:00
}
private void RefreshStatusPanel()
{
_statusPanel.Children.Clear();
2026-03-09 14:14:50 +08:00
var snapshot = _stateService.GetSnapshot();
2026-03-09 12:27:33 +08:00
var basis = GetLayoutBasis();
2026-03-09 14:14:50 +08:00
var titleSize = Math.Clamp(basis * 0.068, 11, 16);
var detailSize = Math.Clamp(basis * 0.052, 9, 13);
2026-03-09 12:27:33 +08:00
2026-03-09 14:14:50 +08:00
foreach (var entry in snapshot.StatusEntries)
2026-03-09 12:27:33 +08:00
{
var palette = GetPalette(entry.State);
_statusPanel.Children.Add(new Border
{
Background = new SolidColorBrush(palette.Background),
BorderBrush = new SolidColorBrush(palette.Border),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(12),
Padding = new Thickness(10, 8),
Child = new Grid
{
2026-03-09 14:14:50 +08:00
RowDefinitions = new RowDefinitions("Auto,Auto"),
2026-03-09 12:27:33 +08:00
ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto"),
ColumnSpacing = 8,
Children =
{
new Border
{
Width = Math.Clamp(basis * 0.038, 8, 11),
Height = Math.Clamp(basis * 0.038, 8, 11),
CornerRadius = new CornerRadius(999),
Background = new SolidColorBrush(palette.Dot),
VerticalAlignment = VerticalAlignment.Center
},
new TextBlock
{
Text = entry.Title,
FontSize = titleSize,
FontWeight = FontWeight.SemiBold,
Foreground = Brushes.White,
TextWrapping = TextWrapping.Wrap
},
new TextBlock
{
2026-03-09 14:14:50 +08:00
Text = entry.Summary,
2026-03-09 12:27:33 +08:00
FontSize = detailSize,
Foreground = new SolidColorBrush(Color.Parse("#FFD7F2FF")),
HorizontalAlignment = HorizontalAlignment.Right,
TextAlignment = TextAlignment.Right,
VerticalAlignment = VerticalAlignment.Center
2026-03-09 14:14:50 +08:00
},
new TextBlock
{
Text = entry.Detail,
FontSize = detailSize,
Foreground = new SolidColorBrush(Color.Parse("#FFD7F2FF")),
TextWrapping = TextWrapping.Wrap
2026-03-09 12:27:33 +08:00
}
}
}
});
var row = (Grid)((Border)_statusPanel.Children[^1]).Child!;
Grid.SetColumn(row.Children[1], 1);
Grid.SetColumn(row.Children[2], 2);
2026-03-09 14:14:50 +08:00
Grid.SetColumnSpan(row.Children[3], 3);
Grid.SetRow(row.Children[3], 1);
2026-03-09 12:27:33 +08:00
}
}
private void ApplyScale()
{
var basis = GetLayoutBasis();
Padding = new Thickness(Math.Clamp(basis * 0.09, 16, 26));
CornerRadius = new CornerRadius(Math.Clamp(basis * 0.14, 20, 34));
_timeTextBlock.FontSize = Math.Clamp(basis * 0.22, 30, 58);
2026-03-09 14:14:50 +08:00
_subtitleTextBlock.FontSize = Math.Clamp(basis * 0.062, 11, 17);
_statusHost.Padding = new Thickness(Math.Clamp(basis * 0.045, 10, 18));
_statusHost.CornerRadius = new CornerRadius(Math.Clamp(basis * 0.09, 14, 22));
_statusPanel.Spacing = Math.Clamp(basis * 0.024, 6, 10);
2026-03-09 12:27:33 +08:00
}
private double GetLayoutBasis()
{
var width = Bounds.Width > 1 ? Bounds.Width : _context.CellSize * 4;
var height = Bounds.Height > 1 ? Bounds.Height : _context.CellSize * 4;
return Math.Max(_context.CellSize * 4, Math.Min(width, height));
}
private static (Color Background, Color Border, Color Dot) GetPalette(SamplePluginHealthState state)
{
return state switch
{
SamplePluginHealthState.Healthy => (
Color.Parse("#1F0F766E"),
Color.Parse("#4D5EEAD4"),
Color.Parse("#5EEAD4")),
SamplePluginHealthState.Faulted => (
Color.Parse("#29B91C1C"),
Color.Parse("#66F87171"),
Color.Parse("#F87171")),
_ => (
Color.Parse("#1F7C2D12"),
Color.Parse("#66FDBA74"),
Color.Parse("#FDBA74"))
};
}
}