From 55260e0360b03a973ab26b723ad1b7add723e618 Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Fri, 6 Feb 2026 08:19:18 +0300 Subject: [PATCH] refactor: ResouceView Move ResourceView and all related to ui core namespace GSS2.UI.Core Split ResourceView on separate elements Make parts folding depended on size --- GSS2.Test/ViewModels/ResourcesViewModel.cs | 458 ------------------ GSS2.Test/Views/MainView.axaml | 8 +- GSS2.Test/Views/ResourcesView.axaml | 82 ---- .../Behaviors/GridColumnHideBehavior.cs | 42 ++ .../ViewModels/ResourceBarViewModel.cs | 60 +++ .../ViewModels/ResourceChartViewModel.cs | 59 +++ .../ResourceCpuUsageBarsViewModel.cs | 31 ++ GSS2.UI.Core/ViewModels/ResourcesViewModel.cs | 221 +++++++++ GSS2.UI.Core/Views/ResourceBarView.axaml | 9 + GSS2.UI.Core/Views/ResourceBarView.axaml.cs | 11 + GSS2.UI.Core/Views/ResourceChartView.axaml | 13 + GSS2.UI.Core/Views/ResourceChartView.axaml.cs | 11 + .../Views/ResourceCpuUsageBarsView.axaml | 27 ++ .../Views/ResourceCpuUsageBarsView.axaml.cs | 11 + GSS2.UI.Core/Views/ResourcesView.axaml | 28 ++ .../Views/ResourcesView.axaml.cs | 2 +- 16 files changed, 528 insertions(+), 545 deletions(-) delete mode 100644 GSS2.Test/ViewModels/ResourcesViewModel.cs delete mode 100644 GSS2.Test/Views/ResourcesView.axaml create mode 100644 GSS2.UI.Core/Behaviors/GridColumnHideBehavior.cs create mode 100644 GSS2.UI.Core/ViewModels/ResourceBarViewModel.cs create mode 100644 GSS2.UI.Core/ViewModels/ResourceChartViewModel.cs create mode 100644 GSS2.UI.Core/ViewModels/ResourceCpuUsageBarsViewModel.cs create mode 100644 GSS2.UI.Core/ViewModels/ResourcesViewModel.cs create mode 100644 GSS2.UI.Core/Views/ResourceBarView.axaml create mode 100644 GSS2.UI.Core/Views/ResourceBarView.axaml.cs create mode 100644 GSS2.UI.Core/Views/ResourceChartView.axaml create mode 100644 GSS2.UI.Core/Views/ResourceChartView.axaml.cs create mode 100644 GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml create mode 100644 GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml.cs create mode 100644 GSS2.UI.Core/Views/ResourcesView.axaml rename {GSS2.Test => GSS2.UI.Core}/Views/ResourcesView.axaml.cs (83%) diff --git a/GSS2.Test/ViewModels/ResourcesViewModel.cs b/GSS2.Test/ViewModels/ResourcesViewModel.cs deleted file mode 100644 index cd8ff5f..0000000 --- a/GSS2.Test/ViewModels/ResourcesViewModel.cs +++ /dev/null @@ -1,458 +0,0 @@ -using System.Collections.ObjectModel; - -using CommunityToolkit.Mvvm.ComponentModel; - -using Avalonia.Threading; - -using SkiaSharp; - -using LiveChartsCore; -using LiveChartsCore.SkiaSharpView; -using LiveChartsCore.SkiaSharpView.Painting; -using LiveChartsCore.Defaults; - -using GSS2.Core.Hardware; -using GSS2.UI.Core.ViewModels; - -namespace GSS2.Test.ViewModels; - -public partial class ResourcesViewModel : ViewModelBase, IDisposable -{ - private bool _disposedValue; - private readonly ComputeResourcesService _computeResourcesService; - private readonly ILogger _logger; - - [ObservableProperty] private ObservableCollection _cpuUsageSeries; - [ObservableProperty] private ObservableCollection _cpuUsageSeriesYAxes; - [ObservableProperty] private ObservableCollection _cpuUsageSeriesXAxes; - - [ObservableProperty] private ObservableCollection _cpuUsagePieSeries; - [ObservableProperty] private ObservableCollection _cpu0UsagePieSeries; - [ObservableProperty] private ObservableCollection _cpu1UsagePieSeries; - [ObservableProperty] private ObservableCollection _cpu2UsagePieSeries; - [ObservableProperty] private ObservableCollection _cpu3UsagePieSeries; - - [ObservableProperty] private ObservableCollection _cpuTemperatureSeries; - - [ObservableProperty] private ObservableCollection _cpuTemperatureSeriesYAxes; - [ObservableProperty] private ObservableCollection _cpuTemperatureSeriesXAxes; - [ObservableProperty] private ObservableCollection _cpuTemperaturePieSeries; - - [ObservableProperty] private ObservableCollection _memoryUsageSeries; - [ObservableProperty] private ObservableCollection _memoryUsageSeriesYAxes; - [ObservableProperty] private ObservableCollection _memoryUsageSeriesXAxes; - [ObservableProperty] private ObservableCollection _memoryUsagePieSeries; - - [ObservableProperty] private int _intervalMs = 500; - [ObservableProperty] private int _chartsHistorySize = 120; - private readonly System.Timers.Timer _updateTimer; - - public ResourcesViewModel(ComputeResourcesService computeResourcesService, ILogger logger) - { - _computeResourcesService = computeResourcesService; - _logger = logger; - - CpuUsageSeries = [ - new LineSeries - { - Name = "Нагрузка ЦПУ, %", - Values = new ObservableCollection(Enumerable.Repeat(0.0, _chartsHistorySize)), - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(144), 0), - GeometrySize = 0, - ShowDataLabels = false, - Stroke = new SolidColorPaint(SKColors.DeepSkyBlue, 1), - GeometryFill = new SolidColorPaint(SKColors.DeepSkyBlue, 0), - GeometryStroke = new SolidColorPaint(SKColors.Transparent, 0), - IsHoverable = false - } - ]; - CpuUsageSeriesYAxes = [ - new Axis - { - MinLimit = 0.0, - MaxLimit = 100.0, - Name = "Нагрузка ЦПУ, %", - NameTextSize = 14, - MinStep = 10, - ForceStepToMin = true - } - ]; - CpuUsageSeriesXAxes = [ - new Axis - { - Labels = new ObservableCollection(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" }, - LabelsAlignment = LiveChartsCore.Drawing.Align.Start - } - ]; - - CpuUsagePieSeries = [ - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue), - InnerRadius = 100, - IsHoverable = false, - DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %", - DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, - DataLabelsSize = 14, - DataLabelsPaint = new SolidColorPaint(SKColors.White) - }, - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), - InnerRadius = 100, - ShowDataLabels = false, - IsHoverable = false - } - ]; - Cpu0UsagePieSeries = [ - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue), - InnerRadius = 50, - IsHoverable = false, - DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %", - DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, - DataLabelsSize = 14, - DataLabelsPaint = new SolidColorPaint(SKColors.White) - }, - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), - InnerRadius = 50, - ShowDataLabels = false, - IsHoverable = false - } - ]; - Cpu1UsagePieSeries = [ - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue), - InnerRadius = 50, - IsHoverable = false, - DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %", - DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, - DataLabelsSize = 14, - DataLabelsPaint = new SolidColorPaint(SKColors.White) - }, - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), - InnerRadius = 50, - ShowDataLabels = false, - IsHoverable = false - } - ]; - Cpu2UsagePieSeries = [ - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue), - InnerRadius = 50, - IsHoverable = false, - DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %", - DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, - DataLabelsSize = 14, - DataLabelsPaint = new SolidColorPaint(SKColors.White) - }, - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), - InnerRadius = 50, - ShowDataLabels = false, - IsHoverable = false - } - ]; - Cpu3UsagePieSeries = [ - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue), - InnerRadius = 50, - IsHoverable = false, - DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %", - DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, - DataLabelsSize = 14, - DataLabelsPaint = new SolidColorPaint(SKColors.White) - }, - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), - InnerRadius = 50, - ShowDataLabels = false, - IsHoverable = false - } - ]; - - CpuTemperatureSeries = [ - new LineSeries - { - Name = "Температура ЦПУ, ℃", - Values = new ObservableCollection(Enumerable.Repeat(0.0, _chartsHistorySize)), - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(144), 0), - GeometrySize = 0, - ShowDataLabels = false, - Stroke = new SolidColorPaint(SKColors.DeepSkyBlue, 1), - GeometryFill = new SolidColorPaint(SKColors.DeepSkyBlue, 0), - GeometryStroke = new SolidColorPaint(SKColors.Transparent, 0), - IsHoverable = false - } - ]; - CpuTemperatureSeriesYAxes = [ - new Axis - { - MinLimit = 0.0, - MaxLimit = 100.0, - Name = "Температура ЦПУ, ℃", - NameTextSize = 14, - MinStep = 10, - ForceStepToMin = true - } - ]; - CpuTemperatureSeriesXAxes = [ - new Axis - { - Labels = new ObservableCollection(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" }, - LabelsAlignment = LiveChartsCore.Drawing.Align.Start, - } - ]; - - CpuTemperaturePieSeries = [ - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue), - InnerRadius = 100, - IsHoverable = false, - DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} ℃", - DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, - DataLabelsSize = 14, - DataLabelsPaint = new SolidColorPaint(SKColors.White) - }, - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), - InnerRadius = 100, - ShowDataLabels = false, - IsHoverable = false - } - ]; - - MemoryUsageSeries = [ - new LineSeries - { - Name = "Нагрузка ОЗУ, МБ", - Values = new ObservableCollection(Enumerable.Repeat(0.0, _chartsHistorySize)), - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(144), 0), - GeometrySize = 0, - ShowDataLabels = false, - Stroke = new SolidColorPaint(SKColors.DeepSkyBlue, 1), - GeometryFill = new SolidColorPaint(SKColors.Transparent, 0), - GeometryStroke = new SolidColorPaint(SKColors.Transparent, 0), - IsHoverable = false - } - ]; - MemoryUsageSeriesYAxes = [ - new Axis - { - MinLimit = 0.0, - MaxLimit = 1000.0, - Name = "Нагрузка ОЗУ, МБ", - NameTextSize = 14, - MinStep = 1000, - ForceStepToMin = true - } - ]; - MemoryUsageSeriesXAxes = [ - new Axis - { - Labels = new ObservableCollection(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" }, - LabelsAlignment = LiveChartsCore.Drawing.Align.Start - } - ]; - - MemoryUsagePieSeries = [ - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue), - InnerRadius = 100, - IsHoverable = false, - DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("000.00")} МБ", - DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, - DataLabelsSize = 14, - DataLabelsPaint = new SolidColorPaint(SKColors.White) - }, - new PieSeries - { - Values = new ObservableCollection { new ObservableValue(1) }, - Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), - InnerRadius = 100, - ShowDataLabels = false, - IsHoverable = false - } - ]; - - Update(); - - _updateTimer = new System.Timers.Timer(IntervalMs); - _updateTimer.Elapsed += UpdateTimerElapsed; - _updateTimer.AutoReset = true; - _updateTimer.Start(); - _logger.LogInformation("{} initialized", nameof(ResourcesViewModel)); - } - - private void UpdateTimerElapsed(object? sender, System.Timers.ElapsedEventArgs ea) => Update(); - private void Update() - { - try - { - var snapshot = _computeResourcesService.GetSnapshot(); - Dispatcher.UIThread.Post(() => - { - if (CpuUsageSeries.Count() == 1 && - CpuUsageSeries.First().Values is ObservableCollection cpuUsageValues) - { - cpuUsageValues.Add(Math.Round(snapshot.CpuUsage.AllUsagePercent, 2)); - while (cpuUsageValues.Count > ChartsHistorySize) - cpuUsageValues.RemoveAt(0); - } - - if (CpuUsagePieSeries.Count() == 2 && - CpuUsagePieSeries.Skip(0).First().Values is ObservableCollection cpuUsagePieValue1 && - cpuUsagePieValue1.Count() == 1 && - CpuUsagePieSeries.Skip(1).First().Values is ObservableCollection cpuUsagePieValue2 && - cpuUsagePieValue2.Count() == 1) - { - var roundedValue = Math.Round(snapshot.CpuUsage.AllUsagePercent, 2); - cpuUsagePieValue1.First().Value = roundedValue; - cpuUsagePieValue2.First().Value = 100 - roundedValue; - } - if (Cpu0UsagePieSeries.Count() == 2 && - Cpu0UsagePieSeries.Skip(0).First().Values is ObservableCollection cpu0UsagePieValue1 && - cpu0UsagePieValue1.Count() == 1 && - Cpu0UsagePieSeries.Skip(1).First().Values is ObservableCollection cpu0UsagePieValue2 && - cpu0UsagePieValue2.Count() == 1 && - snapshot.CpuUsage.CoreUsagePercents.Count() == 4) - { - var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(0).First(), 2); - cpu0UsagePieValue1.First().Value = roundedValue; - cpu0UsagePieValue2.First().Value = 100 - roundedValue; - } - if (Cpu1UsagePieSeries.Count() == 2 && - Cpu1UsagePieSeries.Skip(0).First().Values is ObservableCollection cpu1UsagePieValue1 && - cpu1UsagePieValue1.Count() == 1 && - Cpu1UsagePieSeries.Skip(1).First().Values is ObservableCollection cpu1UsagePieValue2 && - cpu1UsagePieValue2.Count() == 1 && - snapshot.CpuUsage.CoreUsagePercents.Count() == 4) - { - var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(1).First(), 2); - cpu1UsagePieValue1.First().Value = roundedValue; - cpu1UsagePieValue2.First().Value = 100 - roundedValue; - } - if (Cpu2UsagePieSeries.Count() == 2 && - Cpu2UsagePieSeries.Skip(0).First().Values is ObservableCollection cpu2UsagePieValue1 && - cpu2UsagePieValue1.Count() == 1 && - Cpu2UsagePieSeries.Skip(1).First().Values is ObservableCollection cpu2UsagePieValue2 && - cpu2UsagePieValue2.Count() == 1 && - snapshot.CpuUsage.CoreUsagePercents.Count() == 4) - { - var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(2).First(), 2); - cpu2UsagePieValue1.First().Value = roundedValue; - cpu2UsagePieValue2.First().Value = 100 - roundedValue; - } - if (Cpu3UsagePieSeries.Count() == 2 && - Cpu3UsagePieSeries.Skip(0).First().Values is ObservableCollection cpu3UsagePieValue1 && - cpu3UsagePieValue1.Count() == 1 && - Cpu3UsagePieSeries.Skip(1).First().Values is ObservableCollection cpu3UsagePieValue2 && - cpu3UsagePieValue2.Count() == 1 && - snapshot.CpuUsage.CoreUsagePercents.Count() == 4) - { - var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(3).First(), 2); - cpu3UsagePieValue1.First().Value = roundedValue; - cpu3UsagePieValue2.First().Value = 100 - roundedValue; - } - - if (CpuTemperatureSeries.Count() == 1 && - CpuTemperatureSeries.First().Values is ObservableCollection cpuTemperatureValues) - { - cpuTemperatureValues.Add(Math.Round(snapshot.CpuTemperature.Current, 2)); - while (cpuTemperatureValues.Count > ChartsHistorySize) - cpuTemperatureValues.RemoveAt(0); - } - if (CpuTemperatureSeriesYAxes.Count() == 1) - CpuTemperatureSeriesYAxes.First().MaxLimit = Math.Round(snapshot.CpuTemperature.Critical); - - if (CpuTemperaturePieSeries.Count() == 2 && - CpuTemperaturePieSeries.Skip(0).First().Values is ObservableCollection cpuTemperaturePieValue1 && - cpuTemperaturePieValue1.Count() == 1 && - CpuTemperaturePieSeries.Skip(1).First().Values is ObservableCollection cpuTemperaturePieValue2 && - cpuTemperaturePieValue2.Count() == 1) - { - var roundedValue = Math.Round(snapshot.CpuTemperature.Current, 2); - cpuTemperaturePieValue1.First().Value = roundedValue; - cpuTemperaturePieValue2.First().Value = snapshot.CpuTemperature.Critical - roundedValue; - } - - if (MemoryUsageSeries.Count() == 1 && - MemoryUsageSeries.First().Values is ObservableCollection memoryUsageValues) - { - memoryUsageValues.Add(Math.Round(snapshot.MemoryUsage.UsedMB, 2)); - while (memoryUsageValues.Count > ChartsHistorySize) - memoryUsageValues.RemoveAt(0); - // _logger.LogInformation("Memory leakage: {} MB/s", (memoryUsageValues.TakeLast(10).Average() - memoryUsageValues.TakeLast(20).Take(10).Average()) / 5.0); - } - if (MemoryUsageSeriesYAxes.Count() == 1) - MemoryUsageSeriesYAxes.First().MaxLimit = Math.Round(snapshot.MemoryUsage.TotalMB); - - if (MemoryUsagePieSeries.Count() == 2 && - MemoryUsagePieSeries.Skip(0).First().Values is ObservableCollection memoryUsagePieValue1 && - memoryUsagePieValue1.Count() == 1 && - MemoryUsagePieSeries.Skip(1).First().Values is ObservableCollection memoryUsagePieValue2 && - memoryUsagePieValue2.Count() == 1) - { - var roundedValue = Math.Round(snapshot.MemoryUsage.UsedMB, 2); - memoryUsagePieValue1.First().Value = roundedValue; - memoryUsagePieValue2.First().Value = snapshot.MemoryUsage.TotalMB - roundedValue; - } - }); - } - catch (Exception e) - { - _logger.LogWarning(e, "Exception while update"); - } - - } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - try - { - _updateTimer.Stop(); - _updateTimer.Dispose(); - } - catch - { } - } - - _disposedValue = true; - } - } - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } -} \ No newline at end of file diff --git a/GSS2.Test/Views/MainView.axaml b/GSS2.Test/Views/MainView.axaml index 1e56832..75b0bbb 100644 --- a/GSS2.Test/Views/MainView.axaml +++ b/GSS2.Test/Views/MainView.axaml @@ -5,8 +5,8 @@ xmlns:core_vm="using:GSS2.UI.Core.ViewModels" x:DataType="local_vm:MainViewModel" x:Class="GSS2.Test.Views.MainView"> - - - - + + + + \ No newline at end of file diff --git a/GSS2.Test/Views/ResourcesView.axaml b/GSS2.Test/Views/ResourcesView.axaml deleted file mode 100644 index 0b41203..0000000 --- a/GSS2.Test/Views/ResourcesView.axaml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/GSS2.UI.Core/Behaviors/GridColumnHideBehavior.cs b/GSS2.UI.Core/Behaviors/GridColumnHideBehavior.cs new file mode 100644 index 0000000..ed027d1 --- /dev/null +++ b/GSS2.UI.Core/Behaviors/GridColumnHideBehavior.cs @@ -0,0 +1,42 @@ +// Оригинальное решение для проблеммы сворачивания колнок в Grid от +// https://github.com/AvaloniaUI/Avalonia/discussions/6773#discussioncomment-1514604 + +using Avalonia; +using Avalonia.Controls; + +namespace GSS2.UI.Core.Behaviors; + +public static class GridColumnHideBehavior +{ + private readonly static GridLength ZeroWidth = new GridLength(0, GridUnitType.Pixel); + + private readonly static AttachedProperty LastWidthProperty = + AvaloniaProperty.RegisterAttached("LastWidth", typeof(GridColumnHideBehavior), default); + + public readonly static AttachedProperty IsVisibleProperty = + AvaloniaProperty.RegisterAttached("IsVisible", typeof(GridColumnHideBehavior), true, + coerce: (element, visibility) => + { + + var lastWidth = element.GetValue(LastWidthProperty); + if (visibility && lastWidth is { }) + element.SetValue(ColumnDefinition.WidthProperty, lastWidth); + else if (!visibility) + { + element.SetValue(LastWidthProperty, element.GetValue(ColumnDefinition.WidthProperty)); + element.SetValue(ColumnDefinition.WidthProperty, ZeroWidth); + } + return visibility; + } + ); + + public static bool GetIsVisible(ColumnDefinition columnDefinition) + { + return columnDefinition.GetValue(IsVisibleProperty); + } + + public static void SetIsVisible(ColumnDefinition columnDefinition, bool visibility) + { + columnDefinition.SetValue(IsVisibleProperty, visibility); + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/ViewModels/ResourceBarViewModel.cs b/GSS2.UI.Core/ViewModels/ResourceBarViewModel.cs new file mode 100644 index 0000000..4f730ad --- /dev/null +++ b/GSS2.UI.Core/ViewModels/ResourceBarViewModel.cs @@ -0,0 +1,60 @@ +using System.Collections.ObjectModel; + +using Avalonia; + +using CommunityToolkit.Mvvm.ComponentModel; + +using LiveChartsCore; +using LiveChartsCore.Defaults; +using LiveChartsCore.SkiaSharpView; + +namespace GSS2.UI.Core.ViewModels; + +public partial class ResourceBarViewModel : ViewModelBase +{ + [ObservableProperty] private ObservableCollection _series; + [ObservableProperty] private Rect _bounds; + + public ResourceBarViewModel(IEnumerable series) + { + Series = new ObservableCollection(series); + } + + partial void OnBoundsChanged(Rect value) + { + var size = Math.Min(value.Width, value.Height); + + var textSize = size switch + { + < 100 => 8, + < 150 => 10, + < 175 => 12, + _ => 14 + }; + var innerRadius = size switch + { + < 50 => size / 2, + < 120 => size / 3, + _ => size / 4, + }; + + foreach (var series in Series.OfType>()) + { + series.DataLabelsSize = textSize; + series.InnerRadius = innerRadius; + series.OuterRadiusOffset = 0; + series.RelativeOuterRadius = 1; + } + } + + public void UpdateValue(double value, double maxValue) + { + if (Series.Count() == 2 && + Series.Skip(0).First().Values is ObservableCollection value1 && + Series.Skip(1).First().Values is ObservableCollection value2) + { + value1.First().Value = value > maxValue ? maxValue : value; + value2.First().Value = value > maxValue ? 0 : maxValue - value; + } + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/ViewModels/ResourceChartViewModel.cs b/GSS2.UI.Core/ViewModels/ResourceChartViewModel.cs new file mode 100644 index 0000000..af6f0bf --- /dev/null +++ b/GSS2.UI.Core/ViewModels/ResourceChartViewModel.cs @@ -0,0 +1,59 @@ +using System.Collections.ObjectModel; + +using Avalonia; + +using CommunityToolkit.Mvvm.ComponentModel; + +using LiveChartsCore; +using LiveChartsCore.SkiaSharpView; + +namespace GSS2.UI.Core.ViewModels; + +public partial class ResourceChartViewModel : ViewModelBase +{ + [ObservableProperty] private ObservableCollection _series; + [ObservableProperty] private ObservableCollection _yAxes; + [ObservableProperty] private ObservableCollection _xAxes; + [ObservableProperty] private Rect _bounds; + + public ResourceChartViewModel(IEnumerable series, IEnumerable yAxes, IEnumerable xAxes) + { + Series = new ObservableCollection(series); + YAxes = new ObservableCollection(yAxes); + XAxes = new ObservableCollection(xAxes); + } + + partial void OnBoundsChanged(Rect value) + { + var textSize = value.Height switch + { + < 100 => 8, + < 150 => 10, + < 200 => 12, + _ => 14 + }; + var labelsDensity = value.Height switch + { + < 100 => 0.2f, + < 150 => 0.3f, + < 200 => 0.4f, + _ => 0.5f + }; + foreach (var axis in YAxes) + { + axis.TextSize = textSize; + axis.NameTextSize = textSize; + axis.LabelsDensity = labelsDensity; + } + } + + public void AppendValue(double value, int maxValuesCount) + { + if (Series.Count() != 1 || Series.First().Values is not ObservableCollection values) + return; + + values.Add(value); + while (values.Count() > maxValuesCount || values.Count() == 0) + values.RemoveAt(0); + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/ViewModels/ResourceCpuUsageBarsViewModel.cs b/GSS2.UI.Core/ViewModels/ResourceCpuUsageBarsViewModel.cs new file mode 100644 index 0000000..1b314a0 --- /dev/null +++ b/GSS2.UI.Core/ViewModels/ResourceCpuUsageBarsViewModel.cs @@ -0,0 +1,31 @@ +using Avalonia; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace GSS2.UI.Core.ViewModels; + +public partial class ResourceCpuUsageBarsViewModel : ViewModelBase +{ + [ObservableProperty] private Rect _bounds; + [ObservableProperty] private bool _isCoresColumnVisible; + + [ObservableProperty] private ResourceBarViewModel _cpu; + [ObservableProperty] private ResourceBarViewModel _cpu0; + [ObservableProperty] private ResourceBarViewModel _cpu1; + [ObservableProperty] private ResourceBarViewModel _cpu2; + [ObservableProperty] private ResourceBarViewModel _cpu3; + + public ResourceCpuUsageBarsViewModel(ResourceBarViewModel cpu, ResourceBarViewModel cpu0, ResourceBarViewModel cpu1, ResourceBarViewModel cpu2, ResourceBarViewModel cpu3) + { + Cpu = cpu; + Cpu0 = cpu0; + Cpu1 = cpu1; + Cpu2 = cpu2; + Cpu3 = cpu3; + } + + partial void OnBoundsChanged(Rect value) + { + IsCoresColumnVisible = Math.Min(value.Width, value.Height * 1.5) > 200; + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/ViewModels/ResourcesViewModel.cs b/GSS2.UI.Core/ViewModels/ResourcesViewModel.cs new file mode 100644 index 0000000..8019ed6 --- /dev/null +++ b/GSS2.UI.Core/ViewModels/ResourcesViewModel.cs @@ -0,0 +1,221 @@ +using System.Collections.ObjectModel; + +using Microsoft.Extensions.Logging; + +using CommunityToolkit.Mvvm.ComponentModel; + +using Avalonia.Threading; + +using SkiaSharp; + +using LiveChartsCore.SkiaSharpView; +using LiveChartsCore.SkiaSharpView.Painting; +using LiveChartsCore.Defaults; + +using GSS2.Core.Hardware; +using Avalonia; + +namespace GSS2.UI.Core.ViewModels; + +public partial class ResourcesViewModel : ViewModelBase, IDisposable +{ + private bool _disposedValue; + private readonly ComputeResourcesService _computeResourcesService; + private readonly ILogger _logger; + private readonly int _intervalMs = 500; + private readonly int _chartsHistorySize = 120; + private readonly System.Timers.Timer _updateTimer; + + [ObservableProperty] private Rect _bounds; + [ObservableProperty] private bool _isChartsColumnVisible; + [ObservableProperty] private bool _isBarsColumnVisible; + + [ObservableProperty] private ResourceChartViewModel _cpuUsageChartViewModel; + [ObservableProperty] private ResourceChartViewModel _cpuTemperatureChartViewModel; + [ObservableProperty] private ResourceChartViewModel _memoryUsageChartViewModel; + + [ObservableProperty] private ResourceCpuUsageBarsViewModel _cpuUsageBarsViewModel; + [ObservableProperty] private ResourceBarViewModel _cpuTemperatureBarViewModel; + [ObservableProperty] private ResourceBarViewModel _memoryUsageBarViewModel; + + public ResourcesViewModel(ComputeResourcesService computeResourcesService, ILogger logger) + { + _computeResourcesService = computeResourcesService; + _logger = logger; + + ResourceChartViewModel CreateChartViewModel(string name, double minLimit, double maxLimit, double minStep) + { + return new ResourceChartViewModel( + series: + [ + new LineSeries + { + Name = name, + Values = new ObservableCollection(Enumerable.Repeat(0.0, _chartsHistorySize)), + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(127), 0), + GeometrySize = 0, + ShowDataLabels = false, + Stroke = new SolidColorPaint(SKColors.DeepSkyBlue, 1), + GeometryFill = new SolidColorPaint(SKColors.Transparent, 0), + GeometryStroke = new SolidColorPaint(SKColors.Transparent, 0), + IsHoverable = false, + DataPadding = new LiveChartsCore.Drawing.LvcPoint(0.1f, 0.1f) + } + ], + yAxes: + [ + new Axis + { + MinLimit = minLimit, + MaxLimit = maxLimit, + MinStep = minStep, + Name = name + } + ], + xAxes: + [ + new Axis + { + IsVisible = false, + Padding = new LiveChartsCore.Drawing.Padding(0) + } + ] + ); + } + ResourceBarViewModel CreateBarViewModel(Func, string> formater) + { + return new ResourceBarViewModel( + series: + [ + new PieSeries + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue, 0), + ShowDataLabels = true, + IsHoverable = false, + HoverPushout = 0, + DataLabelsFormatter = formater, + DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter, + DataLabelsPaint = new SolidColorPaint(SKColors.White) + }, + new PieSeries + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64), 0), + ShowDataLabels = false, + IsHoverable = false, + HoverPushout = 0 + } + ] + ); + } + + CpuUsageChartViewModel = CreateChartViewModel("Нагрузка ЦПУ, %", 0, 100, 10); + CpuTemperatureChartViewModel = CreateChartViewModel("Температура ЦПУ, ℃", 0, 100, 10); + MemoryUsageChartViewModel = CreateChartViewModel("Нагрузка ОЗУ, МБ", 0, 8000, 1000); + + CpuUsageBarsViewModel = new ResourceCpuUsageBarsViewModel( + CreateBarViewModel(value => $"{value.Model?.Value?.ToString("00.00")} %"), + CreateBarViewModel(value => $"{value.Model?.Value?.ToString("00.00")} %"), + CreateBarViewModel(value => $"{value.Model?.Value?.ToString("00.00")} %"), + CreateBarViewModel(value => $"{value.Model?.Value?.ToString("00.00")} %"), + CreateBarViewModel(value => $"{value.Model?.Value?.ToString("00.00")} %") + ); + CpuTemperatureBarViewModel = CreateBarViewModel(value => $"{value.Model?.Value?.ToString("00.00")} ℃"); + MemoryUsageBarViewModel = CreateBarViewModel(value => $"{value.Model?.Value?.ToString("000.00")} МБ"); + + Update(); + + _updateTimer = new System.Timers.Timer(_intervalMs); + _updateTimer.Elapsed += UpdateTimerElapsed; + _updateTimer.AutoReset = true; + _updateTimer.Start(); + _logger.LogInformation("{} initialized", nameof(ResourcesViewModel)); + } + + partial void OnBoundsChanged(Rect value) + { + _logger.LogInformation("BoundsChanged {}", value); + if (value.Width < 300) + { + IsChartsColumnVisible = false; + IsBarsColumnVisible = true; + } + else if (value.Width < 400) + { + IsChartsColumnVisible = true; + IsBarsColumnVisible = false; + } + else + { + IsChartsColumnVisible = true; + IsBarsColumnVisible = true; + } + } + + partial void OnIsChartsColumnVisibleChanged(bool value) + { + _logger.LogInformation("IsChartsColumnVisibleChanged {}", value); + } + + partial void OnIsBarsColumnVisibleChanged(bool value) + { + _logger.LogInformation("IsBarsColumnVisibleChanged {}", value); + } + + private void UpdateTimerElapsed(object? sender, System.Timers.ElapsedEventArgs ea) => Update(); + private void Update() + { + try + { + var snapshot = _computeResourcesService.GetSnapshot(); + Dispatcher.UIThread.Post(() => + { + if (CpuTemperatureChartViewModel.YAxes.Count() == 1) + CpuTemperatureChartViewModel.YAxes.First().MaxLimit = snapshot.CpuTemperature.Critical; + if (MemoryUsageChartViewModel.YAxes.Count() == 1) + MemoryUsageChartViewModel.YAxes.First().MaxLimit = snapshot.MemoryUsage.TotalMB; + + CpuUsageChartViewModel.AppendValue(Math.Round(snapshot.CpuUsage.AllUsagePercent, 2), _chartsHistorySize); + CpuTemperatureChartViewModel.AppendValue(Math.Round(snapshot.CpuTemperature.Current, 2), _chartsHistorySize); + MemoryUsageChartViewModel.AppendValue(Math.Round(snapshot.MemoryUsage.UsedMB, 2), _chartsHistorySize); + + CpuUsageBarsViewModel.Cpu.UpdateValue(Math.Round(snapshot.CpuUsage.AllUsagePercent, 2), 100); + CpuUsageBarsViewModel.Cpu0.UpdateValue(Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(0).First(), 2), 100); + CpuUsageBarsViewModel.Cpu1.UpdateValue(Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(1).First(), 2), 100); + CpuUsageBarsViewModel.Cpu2.UpdateValue(Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(2).First(), 2), 100); + CpuUsageBarsViewModel.Cpu3.UpdateValue(Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(3).First(), 2), 100); + CpuTemperatureBarViewModel.UpdateValue(Math.Round(snapshot.CpuTemperature.Current, 2), snapshot.CpuTemperature.Critical); + MemoryUsageBarViewModel.UpdateValue(Math.Round(snapshot.MemoryUsage.UsedMB, 2), snapshot.MemoryUsage.TotalMB); + }); + } + catch (Exception e) + { + _logger.LogWarning(e, "Exception while update"); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + try + { + _updateTimer.Stop(); + _updateTimer.Dispose(); + } + catch + { } + } + + _disposedValue = true; + } + } + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/Views/ResourceBarView.axaml b/GSS2.UI.Core/Views/ResourceBarView.axaml new file mode 100644 index 0000000..c9b015f --- /dev/null +++ b/GSS2.UI.Core/Views/ResourceBarView.axaml @@ -0,0 +1,9 @@ + + diff --git a/GSS2.UI.Core/Views/ResourceBarView.axaml.cs b/GSS2.UI.Core/Views/ResourceBarView.axaml.cs new file mode 100644 index 0000000..e2c865d --- /dev/null +++ b/GSS2.UI.Core/Views/ResourceBarView.axaml.cs @@ -0,0 +1,11 @@ +using LiveChartsCore.SkiaSharpView.Avalonia; + +namespace GSS2.UI.Core.Views; + +public partial class ResourceBarView : PieChart +{ + public ResourceBarView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/Views/ResourceChartView.axaml b/GSS2.UI.Core/Views/ResourceChartView.axaml new file mode 100644 index 0000000..f465d4c --- /dev/null +++ b/GSS2.UI.Core/Views/ResourceChartView.axaml @@ -0,0 +1,13 @@ + + diff --git a/GSS2.UI.Core/Views/ResourceChartView.axaml.cs b/GSS2.UI.Core/Views/ResourceChartView.axaml.cs new file mode 100644 index 0000000..dd7d199 --- /dev/null +++ b/GSS2.UI.Core/Views/ResourceChartView.axaml.cs @@ -0,0 +1,11 @@ +using LiveChartsCore.SkiaSharpView.Avalonia; + +namespace GSS2.UI.Core.Views; + +public partial class ResourceChartView : CartesianChart +{ + public ResourceChartView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml b/GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml new file mode 100644 index 0000000..e1d35f6 --- /dev/null +++ b/GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml.cs b/GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml.cs new file mode 100644 index 0000000..4d41257 --- /dev/null +++ b/GSS2.UI.Core/Views/ResourceCpuUsageBarsView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace GSS2.UI.Core.Views; + +public partial class ResourceCpuUsageBarsView : UserControl +{ + public ResourceCpuUsageBarsView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/GSS2.UI.Core/Views/ResourcesView.axaml b/GSS2.UI.Core/Views/ResourcesView.axaml new file mode 100644 index 0000000..72f0f77 --- /dev/null +++ b/GSS2.UI.Core/Views/ResourcesView.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/GSS2.Test/Views/ResourcesView.axaml.cs b/GSS2.UI.Core/Views/ResourcesView.axaml.cs similarity index 83% rename from GSS2.Test/Views/ResourcesView.axaml.cs rename to GSS2.UI.Core/Views/ResourcesView.axaml.cs index 9ec97d3..cd891fc 100644 --- a/GSS2.Test/Views/ResourcesView.axaml.cs +++ b/GSS2.UI.Core/Views/ResourcesView.axaml.cs @@ -1,6 +1,6 @@ using Avalonia.Controls; -namespace GSS2.Test.Views; +namespace GSS2.UI.Core.Views; public partial class ResourcesView : UserControl {