forked from amkovkov/GranuSightSoftware2
comprehensive update
1) remove unused components: camera view (due it causes segmentation faults), compute resources and all related, illuminator controller (due in useless without camera view), image storage service, temperature and humidity service 2) database schema - reduce tables references, features storing in records themselves in compressed form, add records creation and editing date and time, add separator comment column 3) analysis - rework of pipeline and ui, now database storing only raw data and all display values calculated from it 4) lights service - add reconnection if disconnected 5) add width and height command line arguments 6) fix some typos and other issues
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ using Avalonia.Data.Converters;
|
||||
|
||||
namespace GSS2.UI.Core.Converters;
|
||||
|
||||
public class GreaterThenOrEqualConverter : IValueConverter
|
||||
public class GreaterThanOrEqualConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
+1
-1
@@ -4,7 +4,7 @@ using Avalonia.Data.Converters;
|
||||
|
||||
namespace GSS2.UI.Core.Converters;
|
||||
|
||||
public class LessThenConverter : IValueConverter
|
||||
public class LessThanConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
+1
-1
@@ -4,7 +4,7 @@ using Avalonia.Data.Converters;
|
||||
|
||||
namespace GSS2.UI.Core.Converters;
|
||||
|
||||
public class LessThenOrEqualConverter : IValueConverter
|
||||
public class LessThanOrEqualConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
@@ -1,15 +1,18 @@
|
||||
<Window
|
||||
Height="{Binding Height}"
|
||||
Title="{Binding Title}"
|
||||
Topmost="{Binding Topmost}"
|
||||
Width="{Binding Width}"
|
||||
WindowDecorations="{Binding WindowDecorations}"
|
||||
WindowState="{Binding WindowState}"
|
||||
x:Class="GSS2.UI.Core.MainWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:GSS2.UI.Core.ViewModels"
|
||||
Title="{Binding Title}"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
WindowDecorations="{Binding WindowDecorations}"
|
||||
Topmost="{Binding Topmost}"
|
||||
WindowState="{Binding WindowState}">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
>
|
||||
|
||||
<ContentControl x:Name="MainContent" />
|
||||
|
||||
|
||||
@@ -431,6 +431,10 @@ public class SectionPanel : Panel
|
||||
};
|
||||
|
||||
IsScrolling = true;
|
||||
return animation.RunAsync(control).ContinueWith(_ => Dispatcher.UIThread.Invoke(() => IsScrolling = false));
|
||||
return animation
|
||||
.RunAsync(control)
|
||||
.ContinueWith(_ =>
|
||||
Dispatcher.UIThread.Invoke(() => IsScrolling = false)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,24 @@ using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace GSS2.UI.Core.ViewModels;
|
||||
|
||||
public partial class AsyncImageRecordViewModel : ViewModelBase
|
||||
{
|
||||
private static readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||
|
||||
[ObservableProperty] public partial string? Data { get; set; }
|
||||
[ObservableProperty] public partial string Data { get; set; }
|
||||
[ObservableProperty] public partial IImage? Image { get; set; }
|
||||
[ObservableProperty] public partial bool IsLoading { get; set; } = false;
|
||||
|
||||
public AsyncImageRecordViewModel(string? data = null)
|
||||
public AsyncImageRecordViewModel(string data = "")
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
partial void OnDataChanged(string? value) => _ = LoadAsync();
|
||||
partial void OnDataChanged(string value) => _ = LoadAsync();
|
||||
|
||||
private async Task LoadAsync()
|
||||
{
|
||||
@@ -27,7 +29,7 @@ public partial class AsyncImageRecordViewModel : ViewModelBase
|
||||
|
||||
try
|
||||
{
|
||||
if (Data is not null)
|
||||
if (!string.IsNullOrEmpty(Data))
|
||||
{
|
||||
IsLoading = true;
|
||||
byte[] imageBytes = Convert.FromBase64String(Data);
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using LibCameraSharp;
|
||||
|
||||
using GSS2.Core.Hardware;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace GSS2.UI.Core.ViewModels;
|
||||
|
||||
public partial class CameraViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
private readonly ILogger<CameraViewModel> _logger;
|
||||
private readonly CameraService _cameraService;
|
||||
private readonly System.Timers.Timer _renderTimer;
|
||||
|
||||
public event EventHandler? RenderRequested;
|
||||
|
||||
[ObservableProperty] private Color _background = Colors.Transparent;
|
||||
[ObservableProperty] private Stretch _stretchMode = Stretch.Uniform;
|
||||
[ObservableProperty] private TileMode _tileMode = TileMode.None;
|
||||
|
||||
public CameraViewModel(ILogger<CameraViewModel> logger, CameraService cameraService)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Initialization");
|
||||
|
||||
_cameraService = cameraService;
|
||||
_renderTimer = new System.Timers.Timer(33);
|
||||
_renderTimer.Elapsed += (_, _) =>
|
||||
{
|
||||
if (!_cameraService.IsImageCapturing)
|
||||
RenderRequested?.Invoke(this, new EventArgs());
|
||||
};
|
||||
|
||||
_logger.LogInformation("Initialized");
|
||||
}
|
||||
|
||||
public void StartViewFinder()
|
||||
{
|
||||
_cameraService.StartViewFinder();
|
||||
_renderTimer.Start();
|
||||
}
|
||||
public void StopViewFinder()
|
||||
{
|
||||
_renderTimer.Stop();
|
||||
_cameraService.StopViewFinder();
|
||||
}
|
||||
public bool CanGrabViewFinderFrame() => !_cameraService.IsImageCapturing;
|
||||
public (FrameBuffer, StreamConfiguration)? GrabViewFinderFrame() => _cameraService.GrabViewFinderFrame();
|
||||
public void ReturnViewFinderFrame(FrameBuffer frameBuffer) => _cameraService.ReturnViewFinderFrame(frameBuffer);
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_renderTimer.Stop();
|
||||
_renderTimer.Dispose();
|
||||
}
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Avalonia.Layout;
|
||||
|
||||
using GSS2.Core.Hardware;
|
||||
|
||||
namespace GSS2.UI.Core.ViewModels;
|
||||
|
||||
public partial class IlluminatorControllerViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ILogger<IlluminatorControllerViewModel> _logger;
|
||||
private readonly IlluminatorService _illuminatorService;
|
||||
|
||||
private bool _whiteIntensityChanging = false;
|
||||
private bool _uv365IntensityChanging = false;
|
||||
private bool _uv254IntensityChanging = false;
|
||||
|
||||
[ObservableProperty] private double _whiteIntensity;
|
||||
[ObservableProperty] private double _uv365Intensity;
|
||||
[ObservableProperty] private double _uv254Intensity;
|
||||
[ObservableProperty] private bool _enabled;
|
||||
[ObservableProperty] Orientation _orientation;
|
||||
|
||||
public IlluminatorControllerViewModel(ILogger<IlluminatorControllerViewModel> logger, IlluminatorService illuminatorService)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Initialization");
|
||||
|
||||
_illuminatorService = illuminatorService;
|
||||
|
||||
WhiteIntensity = 0;
|
||||
Uv365Intensity = 0;
|
||||
Uv254Intensity = 0;
|
||||
Orientation = Orientation.Horizontal;
|
||||
Enabled = false;
|
||||
|
||||
_logger.LogInformation("Initialized");
|
||||
}
|
||||
|
||||
partial void OnWhiteIntensityChanged(double value)
|
||||
{
|
||||
if (_whiteIntensityChanging)
|
||||
return;
|
||||
_whiteIntensityChanging = true;
|
||||
_illuminatorService.SetIntensity(white: value);
|
||||
_whiteIntensityChanging = false;
|
||||
}
|
||||
partial void OnUv365IntensityChanged(double value)
|
||||
{
|
||||
if (_uv365IntensityChanging)
|
||||
return;
|
||||
_uv365IntensityChanging = true;
|
||||
_illuminatorService.SetIntensity(uv365: value);
|
||||
_uv365IntensityChanging = false;
|
||||
}
|
||||
partial void OnUv254IntensityChanged(double value)
|
||||
{
|
||||
if (_uv254IntensityChanging)
|
||||
return;
|
||||
_uv254IntensityChanging = true;
|
||||
_illuminatorService.SetIntensity(uv254: value);
|
||||
_uv254IntensityChanging = false;
|
||||
}
|
||||
|
||||
partial void OnEnabledChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
_illuminatorService.TurnOn();
|
||||
else
|
||||
_illuminatorService.TurnOff();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
[ObservableProperty] public partial WindowDecorations WindowDecorations { get; set; }
|
||||
[ObservableProperty] public partial bool Topmost { get; set; }
|
||||
[ObservableProperty] public partial WindowState WindowState { get; set; }
|
||||
[ObservableProperty] public partial double Width { get; set; }
|
||||
[ObservableProperty] public partial double Height { get; set; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.Defaults;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace GSS2.UI.Core.ViewModels;
|
||||
|
||||
public partial class ResourceBarViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ILogger<ResourceBarViewModel> _logger;
|
||||
|
||||
[ObservableProperty] private ObservableCollection<ISeries> _series;
|
||||
[ObservableProperty] private Rect _bounds;
|
||||
|
||||
public ResourceBarViewModel(ILogger<ResourceBarViewModel> logger, IEnumerable<ISeries> series)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Initialization");
|
||||
|
||||
Series = new ObservableCollection<ISeries>(series);
|
||||
|
||||
_logger.LogInformation("Initialized");
|
||||
}
|
||||
|
||||
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<PieSeries<ObservableValue>>())
|
||||
{
|
||||
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<ObservableValue> value1 &&
|
||||
Series.Skip(1).First().Values is ObservableCollection<ObservableValue> value2)
|
||||
{
|
||||
value1.First().Value = value > maxValue ? maxValue : value;
|
||||
value2.First().Value = value > maxValue ? 0 : maxValue - value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
|
||||
namespace GSS2.UI.Core.ViewModels;
|
||||
|
||||
public partial class ResourceChartViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ILogger<ResourceChartViewModel> _logger;
|
||||
[ObservableProperty] private ObservableCollection<ISeries> _series;
|
||||
[ObservableProperty] private ObservableCollection<Axis> _yAxes;
|
||||
[ObservableProperty] private ObservableCollection<Axis> _xAxes;
|
||||
[ObservableProperty] private Rect _bounds;
|
||||
|
||||
public ResourceChartViewModel(ILogger<ResourceChartViewModel> logger, IEnumerable<ISeries> series, IEnumerable<Axis> yAxes, IEnumerable<Axis> xAxes)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Initialization");
|
||||
|
||||
Series = new ObservableCollection<ISeries>(series);
|
||||
YAxes = new ObservableCollection<Axis>(yAxes);
|
||||
XAxes = new ObservableCollection<Axis>(xAxes);
|
||||
|
||||
_logger.LogInformation("Initialized");
|
||||
}
|
||||
|
||||
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<double> values)
|
||||
return;
|
||||
|
||||
values.Add(value);
|
||||
while (values.Count() > maxValuesCount || values.Count() == 0)
|
||||
values.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
namespace GSS2.UI.Core.ViewModels;
|
||||
|
||||
public partial class ResourceCpuUsageBarsViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ILogger<ResourceCpuUsageBarsViewModel> _logger;
|
||||
|
||||
[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(
|
||||
ILogger<ResourceCpuUsageBarsViewModel> logger,
|
||||
ResourceBarViewModel cpu,
|
||||
ResourceBarViewModel cpu0,
|
||||
ResourceBarViewModel cpu1,
|
||||
ResourceBarViewModel cpu2,
|
||||
ResourceBarViewModel cpu3
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Initialized");
|
||||
|
||||
Cpu = cpu;
|
||||
Cpu0 = cpu0;
|
||||
Cpu1 = cpu1;
|
||||
Cpu2 = cpu2;
|
||||
Cpu3 = cpu3;
|
||||
|
||||
_logger.LogInformation("Initialized");
|
||||
}
|
||||
|
||||
partial void OnBoundsChanged(Rect value)
|
||||
{
|
||||
IsCoresColumnVisible = Math.Min(value.Width, value.Height * 1.5) > 200;
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
|
||||
using SkiaSharp;
|
||||
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using LiveChartsCore.Defaults;
|
||||
|
||||
using GSS2.Core.Hardware;
|
||||
|
||||
namespace GSS2.UI.Core.ViewModels;
|
||||
|
||||
public partial class ResourcesViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
private readonly ComputeResourcesService _computeResourcesService;
|
||||
private readonly ILogger<ResourcesViewModel> _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(
|
||||
ILogger<ResourcesViewModel> logger,
|
||||
ILogger<ResourceCpuUsageBarsViewModel> resourceCpuUsageBarsViewModelLogger,
|
||||
ILogger<ResourceChartViewModel> resourceChartViewModelCpuUsageLogger,
|
||||
ILogger<ResourceChartViewModel> resourceChartViewModelCpuTemperatureLogger,
|
||||
ILogger<ResourceChartViewModel> resourceChartViewModelMemoryUsageLogger,
|
||||
ILogger<ResourceBarViewModel> resourceBarViewModelCpuUsageLogger,
|
||||
ILogger<ResourceBarViewModel> resourceBarViewModelCpu0UsageLogger,
|
||||
ILogger<ResourceBarViewModel> resourceBarViewModelCpu1UsageLogger,
|
||||
ILogger<ResourceBarViewModel> resourceBarViewModelCpu2UsageLogger,
|
||||
ILogger<ResourceBarViewModel> resourceBarViewModelCpu3UsageLogger,
|
||||
ILogger<ResourceBarViewModel> resourceBarViewModelCpuTemperatureLogger,
|
||||
ILogger<ResourceBarViewModel> resourceBarViewModelMemoryUsageLogger,
|
||||
ComputeResourcesService computeResourcesService
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Initialization");
|
||||
|
||||
_computeResourcesService = computeResourcesService;
|
||||
|
||||
ResourceChartViewModel CreateChartViewModel(ILogger<ResourceChartViewModel> logger, string name, double minLimit, double maxLimit, double minStep)
|
||||
{
|
||||
return new ResourceChartViewModel(
|
||||
logger: logger,
|
||||
series:
|
||||
[
|
||||
new LineSeries<double>
|
||||
{
|
||||
Name = name,
|
||||
Values = new ObservableCollection<double>(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(ILogger<ResourceBarViewModel> logger, Func<LiveChartsCore.Kernel.ChartPoint<ObservableValue, LiveChartsCore.SkiaSharpView.Drawing.Geometries.DoughnutGeometry, LiveChartsCore.SkiaSharpView.Drawing.Geometries.LabelGeometry>, string> formatter)
|
||||
{
|
||||
return new ResourceBarViewModel(
|
||||
logger: logger,
|
||||
series:
|
||||
[
|
||||
new PieSeries<ObservableValue>
|
||||
{
|
||||
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
|
||||
Fill = new SolidColorPaint(SKColors.DeepSkyBlue, 0),
|
||||
ShowDataLabels = true,
|
||||
IsHoverable = false,
|
||||
HoverPushout = 0,
|
||||
DataLabelsFormatter = formatter,
|
||||
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
|
||||
DataLabelsPaint = new SolidColorPaint(SKColors.White)
|
||||
},
|
||||
new PieSeries<ObservableValue>
|
||||
{
|
||||
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
|
||||
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64), 0),
|
||||
ShowDataLabels = false,
|
||||
IsHoverable = false,
|
||||
HoverPushout = 0
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
CpuUsageChartViewModel = CreateChartViewModel(resourceChartViewModelCpuUsageLogger, "Нагрузка ЦПУ, %", 0, 100, 10);
|
||||
CpuTemperatureChartViewModel = CreateChartViewModel(resourceChartViewModelCpuTemperatureLogger, "Температура ЦПУ, ℃", 0, 100, 10);
|
||||
MemoryUsageChartViewModel = CreateChartViewModel(resourceChartViewModelMemoryUsageLogger, "Нагрузка ОЗУ, МБ", 0, 8000, 1000);
|
||||
|
||||
CpuUsageBarsViewModel = new ResourceCpuUsageBarsViewModel(
|
||||
resourceCpuUsageBarsViewModelLogger,
|
||||
CreateBarViewModel(resourceBarViewModelCpuUsageLogger, value => $"{value.Model?.Value?.ToString("00.00")} %"),
|
||||
CreateBarViewModel(resourceBarViewModelCpu0UsageLogger, value => $"{value.Model?.Value?.ToString("00.00")} %"),
|
||||
CreateBarViewModel(resourceBarViewModelCpu1UsageLogger, value => $"{value.Model?.Value?.ToString("00.00")} %"),
|
||||
CreateBarViewModel(resourceBarViewModelCpu2UsageLogger, value => $"{value.Model?.Value?.ToString("00.00")} %"),
|
||||
CreateBarViewModel(resourceBarViewModelCpu3UsageLogger, value => $"{value.Model?.Value?.ToString("00.00")} %")
|
||||
);
|
||||
CpuTemperatureBarViewModel = CreateBarViewModel(resourceBarViewModelCpuTemperatureLogger, value => $"{value.Model?.Value?.ToString("00.00")} ℃");
|
||||
MemoryUsageBarViewModel = CreateBarViewModel(resourceBarViewModelMemoryUsageLogger, 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");
|
||||
}
|
||||
|
||||
partial void OnBoundsChanged(Rect value)
|
||||
{
|
||||
if (value.Width < 300)
|
||||
{
|
||||
IsChartsColumnVisible = false;
|
||||
IsBarsColumnVisible = true;
|
||||
}
|
||||
else if (value.Width < 400)
|
||||
{
|
||||
IsChartsColumnVisible = true;
|
||||
IsBarsColumnVisible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsChartsColumnVisible = true;
|
||||
IsBarsColumnVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<Control
|
||||
x:Class="GSS2.UI.Core.Views.CameraView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:GSS2.UI.Core.ViewModels"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
x:DataType="vm:CameraViewModel"
|
||||
Background="{Binding Background}"
|
||||
StretchMode="{Binding StretchMode}"
|
||||
TileMode="{Binding TileMode}" />
|
||||
@@ -1,714 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.OpenGL.Egl;
|
||||
using Avalonia.OpenGL.Controls;
|
||||
using Avalonia.OpenGL;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.Input;
|
||||
|
||||
using GSS2.UI.Core.ViewModels;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using GSS2.Core.UI.Extensions;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace GSS2.UI.Core.Views;
|
||||
|
||||
public partial class CameraView : OpenGlControlBase
|
||||
{
|
||||
private class OpenGLContext
|
||||
{
|
||||
[DllImport("libEGL.so.1")] public static extern IntPtr eglQueryString(IntPtr dpy, int name);
|
||||
[DllImport("libEGL.so.1")] public static extern IntPtr eglGetCurrentDisplay();
|
||||
[DllImport("libEGL.so.1")] public static extern IntPtr eglGetProcAddress(string procname);
|
||||
[DllImport("libGL.so.1")] public static extern void glUniform1i(int location, int falue);
|
||||
[DllImport("libGL.so.1")] public static extern void glUniformMatrix3x2fv(int location, int count, bool transpose, IntPtr value);
|
||||
[DllImport("libGL.so.1")] public static extern void glUniformMatrix3fv(int location, int count, bool transpose, IntPtr value);
|
||||
|
||||
public const uint DRM_FORMAT_BGRX8888 = 0x34325258; // XR24 XRGB8888
|
||||
public const int GL_TRIANGLE_STRIP = 0x0005;
|
||||
public const int GL_TEXTURE_WRAP_S = 0x2802;
|
||||
public const int GL_TEXTURE_WRAP_T = 0x2803;
|
||||
public const int GL_CLAMP_TO_EDGE = 0x812F;
|
||||
public const int EGL_LINUX_DMA_BUF_EXT = 0x3270;
|
||||
public const int EGL_LINUX_DRM_FOURCC_EXT = 0x3271;
|
||||
public const int EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272;
|
||||
public const int EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273;
|
||||
public const int EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274;
|
||||
|
||||
public delegate IntPtr GlEGLImageTargetTexture2DOESDelegate(int target, IntPtr image);
|
||||
public delegate IntPtr EglCreateImageKHRDelegate(IntPtr dpy, IntPtr ctx, int target, IntPtr buffer, int[] attribs);
|
||||
public delegate bool EglDestroyImageKHRDelegate(IntPtr dpy, IntPtr image);
|
||||
|
||||
private const string VERTEX_SHADER = @"
|
||||
#version 300 es
|
||||
layout (location = 0) in vec2 aPos;
|
||||
layout (location = 1) in vec2 aUv;
|
||||
|
||||
uniform mat4 uMvp;
|
||||
uniform mat3 uUv;
|
||||
|
||||
out vec2 vUv;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 uv = uUv * vec3(aUv, 1.0);
|
||||
vUv = uv.xy;
|
||||
gl_Position = uMvp * vec4(aPos, 0.0, 1.0);
|
||||
}
|
||||
";
|
||||
private const string FRAGMENT_SHADER = @"
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
in vec2 vUv;
|
||||
uniform sampler2D uTex;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = texture(uTex, vUv);
|
||||
}
|
||||
";
|
||||
private readonly float[] Vertices =
|
||||
[
|
||||
-1f, -1f, 0f, 1f,
|
||||
1f, -1f, 1f, 1f,
|
||||
-1f, 1f, 0f, 0f,
|
||||
1f, 1f, 1f, 0f,
|
||||
];
|
||||
|
||||
public IntPtr EglDisplay { get; }
|
||||
public int Texture { get; }
|
||||
public int Program { get; }
|
||||
public int UTexLocation { get; }
|
||||
public int UMvpLocation { get; }
|
||||
public int UUvLocation { get; }
|
||||
public int VAO { get; }
|
||||
public int VBO { get; }
|
||||
public EglCreateImageKHRDelegate EglCreateImageKHR { get; }
|
||||
public EglDestroyImageKHRDelegate EglDestroyImageKHR { get; }
|
||||
public GlEGLImageTargetTexture2DOESDelegate GlEGLImageTargetTexture2DOES { get; }
|
||||
|
||||
public OpenGLContext(GlInterface gl)
|
||||
{
|
||||
var glExtensions = gl.GetString(GlConsts.GL_EXTENSIONS);
|
||||
if (glExtensions is null)
|
||||
throw new Exception("GL extensions query returned null string");
|
||||
|
||||
if (!glExtensions.Contains("GL_OES_EGL_image") &&
|
||||
!glExtensions.Contains("GL_OES_EGL_image_external"))
|
||||
throw new Exception("Required GL extensions \"GL_OES_EGL_image_external\" or \"GL_OES_EGL_image\" not found");
|
||||
|
||||
EglDisplay = eglGetCurrentDisplay();
|
||||
if (EglDisplay == IntPtr.Zero)
|
||||
throw new Exception("EGL display is nullptr");
|
||||
|
||||
var eglExtensions = Marshal.PtrToStringAnsi(eglQueryString(EglDisplay, EglConsts.EGL_EXTENSIONS));
|
||||
if (eglExtensions is null)
|
||||
throw new Exception("EGL extensions query returned null");
|
||||
|
||||
if (!eglExtensions.Contains("EGL_EXT_image_dma_buf_import"))
|
||||
throw new Exception("Required EGL extension \"EGL_EXT_image_dma_buf_import\" not found");
|
||||
|
||||
var pEglCreateImageKHR = eglGetProcAddress("eglCreateImageKHR");
|
||||
if (pEglCreateImageKHR == IntPtr.Zero)
|
||||
throw new Exception("Required EGL method \"eglCreateImageKHR\" not found");
|
||||
EglCreateImageKHR = Marshal.GetDelegateForFunctionPointer<EglCreateImageKHRDelegate>(pEglCreateImageKHR);
|
||||
|
||||
var pEglDestroyImageKHR = eglGetProcAddress("eglDestroyImageKHR");
|
||||
if (pEglDestroyImageKHR == IntPtr.Zero)
|
||||
throw new Exception("Required EGL method \"eglDestroyImageKHR\" not found");
|
||||
EglDestroyImageKHR = Marshal.GetDelegateForFunctionPointer<EglDestroyImageKHRDelegate>(pEglDestroyImageKHR);
|
||||
|
||||
var pGlEGLImageTargetTexture2DOES = eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
||||
if (pGlEGLImageTargetTexture2DOES == IntPtr.Zero)
|
||||
throw new Exception("Required EGL method \"glEGLImageTargetTexture2DOES\" not found");
|
||||
GlEGLImageTargetTexture2DOES = Marshal.GetDelegateForFunctionPointer<GlEGLImageTargetTexture2DOESDelegate>(pGlEGLImageTargetTexture2DOES);
|
||||
|
||||
Texture = gl.GenTexture(); ThrowIfOpenGLError(gl);
|
||||
gl.BindTexture(GlConsts.GL_TEXTURE_2D, Texture); ThrowIfOpenGLError(gl);
|
||||
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); ThrowIfOpenGLError(gl);
|
||||
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); ThrowIfOpenGLError(gl);
|
||||
|
||||
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MIN_FILTER, GlConsts.GL_LINEAR); ThrowIfOpenGLError(gl);
|
||||
gl.TexParameteri(GlConsts.GL_TEXTURE_2D, GlConsts.GL_TEXTURE_MAG_FILTER, GlConsts.GL_LINEAR); ThrowIfOpenGLError(gl);
|
||||
|
||||
int vertexShader = gl.CreateShader(GlConsts.GL_VERTEX_SHADER); ThrowIfOpenGLError(gl);
|
||||
gl.ShaderSourceString(vertexShader, VERTEX_SHADER); ThrowIfOpenGLError(gl);
|
||||
gl.CompileShader(vertexShader);
|
||||
|
||||
int fragmentShader = gl.CreateShader(GlConsts.GL_FRAGMENT_SHADER); ThrowIfOpenGLError(gl);
|
||||
gl.ShaderSourceString(fragmentShader, FRAGMENT_SHADER); ThrowIfOpenGLError(gl);
|
||||
gl.CompileShader(fragmentShader); ThrowIfOpenGLError(gl);
|
||||
|
||||
Program = gl.CreateProgram(); ThrowIfOpenGLError(gl);
|
||||
gl.AttachShader(Program, vertexShader); ThrowIfOpenGLError(gl);
|
||||
gl.AttachShader(Program, fragmentShader); ThrowIfOpenGLError(gl);
|
||||
gl.LinkProgram(Program); ThrowIfOpenGLError(gl);
|
||||
|
||||
gl.DeleteShader(vertexShader); ThrowIfOpenGLError(gl);
|
||||
gl.DeleteShader(fragmentShader); ThrowIfOpenGLError(gl);
|
||||
|
||||
UTexLocation = gl.GetUniformLocationString(Program, "uTex"); ThrowIfOpenGLError(gl);
|
||||
UMvpLocation = gl.GetUniformLocationString(Program, "uMvp"); ThrowIfOpenGLError(gl);
|
||||
UUvLocation = gl.GetUniformLocationString(Program, "uUv"); ThrowIfOpenGLError(gl);
|
||||
|
||||
VAO = gl.GenVertexArray(); ThrowIfOpenGLError(gl);
|
||||
VBO = gl.GenBuffer(); ThrowIfOpenGLError(gl);
|
||||
|
||||
gl.BindVertexArray(VAO); ThrowIfOpenGLError(gl);
|
||||
|
||||
gl.BindBuffer(GlConsts.GL_ARRAY_BUFFER, VBO); ThrowIfOpenGLError(gl);
|
||||
unsafe
|
||||
{
|
||||
fixed (float* pVertices = Vertices)
|
||||
gl.BufferData(GlConsts.GL_ARRAY_BUFFER, Vertices.Length * sizeof(float), new IntPtr(pVertices), GlConsts.GL_STATIC_DRAW); ThrowIfOpenGLError(gl);
|
||||
}
|
||||
|
||||
gl.EnableVertexAttribArray(0); ThrowIfOpenGLError(gl);
|
||||
gl.VertexAttribPointer(0, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), IntPtr.Zero); ThrowIfOpenGLError(gl);
|
||||
|
||||
gl.EnableVertexAttribArray(1); ThrowIfOpenGLError(gl);
|
||||
gl.VertexAttribPointer(1, 2, GlConsts.GL_FLOAT, 0, 4 * sizeof(float), 2 * sizeof(float)); ThrowIfOpenGLError(gl);
|
||||
|
||||
gl.BindVertexArray(0); ThrowIfOpenGLError(gl);
|
||||
}
|
||||
|
||||
private void ThrowIfOpenGLError(GlInterface gl)
|
||||
{
|
||||
var error = gl.GetError();
|
||||
if (error != 0)
|
||||
throw new Exception($"OpenGL context initialization error: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ILogger<CameraView> _logger;
|
||||
private readonly Dictionary<int, IntPtr> _importedDmaBuffers = new Dictionary<int, nint>();
|
||||
private bool _isViewFinderStarted = false;
|
||||
private OpenGLContext? _openGLContext = null;
|
||||
private Point? _lastMousePosition = null;
|
||||
private Dictionary<int, Point>? _lastTouchPositions = null;
|
||||
private float _zoom = 1;
|
||||
private float _panX = 0;
|
||||
private float _panY = 0;
|
||||
|
||||
public static readonly StyledProperty<Color> BackgroundProperty = AvaloniaProperty.Register<CameraView, Color>(nameof(Background), defaultValue: Colors.Transparent);
|
||||
public Color Background
|
||||
{
|
||||
get => GetValue(BackgroundProperty);
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<Stretch> StretchModeProperty = AvaloniaProperty.Register<CameraView, Stretch>(nameof(StretchMode), defaultValue: Stretch.Uniform);
|
||||
public Stretch StretchMode
|
||||
{
|
||||
get => GetValue(StretchModeProperty);
|
||||
set => SetValue(StretchModeProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<TileMode> TileModeProperty = AvaloniaProperty.Register<CameraView, TileMode>(nameof(TileMode), defaultValue: TileMode.None);
|
||||
public TileMode TileMode
|
||||
{
|
||||
get => GetValue(TileModeProperty);
|
||||
set => SetValue(TileModeProperty, value);
|
||||
}
|
||||
|
||||
public CameraView(ILogger<CameraView> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
_logger.LogInformation("{} initialized", nameof(CameraView));
|
||||
|
||||
DoubleTapped += (_, _) =>
|
||||
{
|
||||
_zoom = 1f;
|
||||
_panX = 0f;
|
||||
_panY = 0f;
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs ea)
|
||||
{
|
||||
if (DataContext is CameraViewModel viewModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
viewModel.StartViewFinder();
|
||||
viewModel.RenderRequested += RenderRequested;
|
||||
_isViewFinderStarted = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogCritical(ex, "Exception while view finder start");
|
||||
}
|
||||
}
|
||||
else
|
||||
_logger.LogCritical("Cannot start view finder, data context is null or not camera view model");
|
||||
base.OnAttachedToVisualTree(ea);
|
||||
}
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs ea)
|
||||
{
|
||||
if (_isViewFinderStarted && DataContext is CameraViewModel viewModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
viewModel.StopViewFinder();
|
||||
foreach (var (fd, image) in _importedDmaBuffers)
|
||||
_openGLContext?.EglDestroyImageKHR!(_openGLContext.EglDisplay, image);
|
||||
_isViewFinderStarted = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogCritical(ex, "Exception while view finder stop");
|
||||
}
|
||||
viewModel.RenderRequested -= RenderRequested;
|
||||
}
|
||||
else if (_isViewFinderStarted)
|
||||
_logger.LogCritical("Cannot stop view finder, data context is null or not camera view model");
|
||||
|
||||
_isViewFinderStarted = false;
|
||||
base.OnDetachedFromLogicalTree(ea);
|
||||
}
|
||||
|
||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs ea)
|
||||
{
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
var position = ea.GetPosition(this);
|
||||
|
||||
// Рассчитываем позицию в координатах изображения (до масштабирования)
|
||||
var imageX = (position.X - Bounds.Width / 2 - _panX) / _zoom;
|
||||
var imageY = (-position.Y + Bounds.Height / 2 - _panY) / _zoom; // Ось Y инвертирована
|
||||
|
||||
// Применяем масштаб
|
||||
float zoomFactor = ea.Delta.Y > 0 ? 1.1f : 1 / 1.1f;
|
||||
_zoom *= zoomFactor;
|
||||
_zoom = Math.Clamp(_zoom, 1f, 10f);
|
||||
_zoom = (float)Math.Round(_zoom, 2);
|
||||
|
||||
if (_zoom != 1f)
|
||||
{
|
||||
// Пересчитываем смещение так, чтобы указатель остался на месте
|
||||
_panX = (float)(position.X - Bounds.Width / 2 - imageX * _zoom);
|
||||
_panY = (float)(-position.Y + Bounds.Height / 2 - imageY * _zoom); // Ось Y инвертирована
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если зум == 1, то возвращаем изображение к исходному смещению
|
||||
_panX = 0f;
|
||||
_panY = 0f;
|
||||
}
|
||||
}
|
||||
base.OnPointerWheelChanged(ea);
|
||||
}
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs ea)
|
||||
{
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
if (ea.Properties.IsLeftButtonPressed)
|
||||
{
|
||||
if (ea.ClickCount == 2) // По двойному клику возвращаем изображение к смещение и зум к стандартным значениям
|
||||
{
|
||||
_zoom = 1f;
|
||||
_panX = 0f;
|
||||
_panY = 0f;
|
||||
}
|
||||
else
|
||||
_lastMousePosition = ea.GetPosition(this);
|
||||
}
|
||||
}
|
||||
else if (ea.Pointer.Type == PointerType.Touch)
|
||||
{
|
||||
if (_lastTouchPositions is null)
|
||||
_lastTouchPositions = new Dictionary<int, Point>();
|
||||
|
||||
if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2)
|
||||
_lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this));
|
||||
else if (_lastTouchPositions.ContainsKey(ea.Pointer.Id))
|
||||
_lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this);
|
||||
}
|
||||
base.OnPointerPressed(ea);
|
||||
}
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs ea)
|
||||
{
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
if (!ea.Properties.IsLeftButtonPressed)
|
||||
_lastMousePosition = null;
|
||||
}
|
||||
else if (ea.Pointer.Type == PointerType.Touch)
|
||||
{
|
||||
if (_lastTouchPositions is not null && _lastTouchPositions.ContainsKey(ea.Pointer.Id))
|
||||
_lastTouchPositions.Remove(ea.Pointer.Id);
|
||||
}
|
||||
base.OnPointerReleased(ea);
|
||||
}
|
||||
protected override void OnPointerMoved(PointerEventArgs ea)
|
||||
{
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
if (_lastMousePosition is null || _zoom == 1) // Если кнопка не нажата или зум == 1, то ничего не делаем
|
||||
{
|
||||
base.OnPointerMoved(ea);
|
||||
return;
|
||||
}
|
||||
var position = ea.GetPosition(this);
|
||||
_panX -= (float)(_lastMousePosition.Value.X - position.X);
|
||||
_panY += (float)(_lastMousePosition.Value.Y - position.Y); // Ось Y инвертирована
|
||||
_lastMousePosition = position;
|
||||
}
|
||||
else if (ea.Pointer.Type == PointerType.Touch)
|
||||
{
|
||||
if (_lastTouchPositions is null)
|
||||
{
|
||||
base.OnPointerMoved(ea);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_lastTouchPositions.ContainsKey(ea.Pointer.Id) && _lastTouchPositions.Count() < 2)
|
||||
{
|
||||
_lastTouchPositions.Add(ea.Pointer.Id, ea.GetPosition(this));
|
||||
base.OnPointerMoved(ea);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastTouchPositions.Count() == 1 && _zoom != 1) // Если есть только одно прикосновение и зум != 1, то только перемещение
|
||||
{
|
||||
var position = ea.GetPosition(this);
|
||||
_panX -= (float)(_lastTouchPositions[ea.Pointer.Id].X - position.X);
|
||||
_panY += (float)(_lastTouchPositions[ea.Pointer.Id].Y - position.Y); // Ось Y инвертирована
|
||||
_lastTouchPositions[ea.Pointer.Id] = position;
|
||||
}
|
||||
else if (_lastTouchPositions.Count() == 2) // Если два прикосновения, то перемещение с приближением
|
||||
{
|
||||
var lastPosition1 = _lastTouchPositions.Skip(0).First().Value;
|
||||
var lastPosition2 = _lastTouchPositions.Skip(1).First().Value;
|
||||
var lastDistance = Point.Distance(lastPosition1, lastPosition2);
|
||||
var lastCenter = PointHelper.Center(lastPosition1, lastPosition2);
|
||||
|
||||
_lastTouchPositions[ea.Pointer.Id] = ea.GetPosition(this);
|
||||
|
||||
var newPosition1 = _lastTouchPositions.Skip(0).First().Value;
|
||||
var newPosition2 = _lastTouchPositions.Skip(1).First().Value;
|
||||
var newDistance = Point.Distance(newPosition1, newPosition2);
|
||||
var newCenter = PointHelper.Center(newPosition1, newPosition2);
|
||||
|
||||
var zoomFactor = newDistance / lastDistance;
|
||||
var newZoom = (float)(_zoom * zoomFactor);
|
||||
newZoom = (float)Math.Clamp(newZoom, 1f, 10f);
|
||||
|
||||
var imageX = (newCenter.X - Bounds.Width / 2 - _panX) / _zoom;
|
||||
var imageY = (-newCenter.Y + Bounds.Height / 2 - _panY) / _zoom; // Ось Y инвертирована
|
||||
|
||||
_zoom = newZoom;
|
||||
|
||||
if (_zoom > 1f)
|
||||
{
|
||||
_panX = (float)(newCenter.X - Bounds.Width / 2 - imageX * _zoom);
|
||||
_panY = (float)(-newCenter.Y + Bounds.Height / 2 - imageY * _zoom); // Ось Y инвертирована
|
||||
}
|
||||
else
|
||||
{
|
||||
_zoom = 1f;
|
||||
_panX = 0f;
|
||||
_panY = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
base.OnPointerMoved(ea);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
context.FillRectangle(new SolidColorBrush(Background), new Rect(0, 0, Bounds.Width, Bounds.Height));
|
||||
base.Render(context);
|
||||
}
|
||||
private void RenderRequested(object? sender, EventArgs ea)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var transformBounds = this.GetTransformedBounds();
|
||||
if (transformBounds is not null &&
|
||||
(transformBounds.Value.Clip.Bottom != 0 ||
|
||||
transformBounds.Value.Clip.Top != 0 ||
|
||||
transformBounds.Value.Clip.Left != 0 ||
|
||||
transformBounds.Value.Clip.Right != 0))
|
||||
RequestNextFrameRendering();
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnOpenGlInit(GlInterface gl)
|
||||
{
|
||||
_logger.LogInformation("OpenGL initialized");
|
||||
_logger.LogInformation("GL version: {}", gl.GetString(GlConsts.GL_VERSION));
|
||||
_logger.LogInformation("GL vendor: {}", gl.GetString(GlConsts.GL_VENDOR));
|
||||
_logger.LogInformation("GL renderer: {}", gl.GetString(GlConsts.GL_RENDERER));
|
||||
try
|
||||
{
|
||||
_openGLContext = new OpenGLContext(gl);
|
||||
_logger.LogInformation("OpenGL context initialized");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogCritical(ex, "Error while OpenGL initialization");
|
||||
}
|
||||
base.OnOpenGlInit(gl);
|
||||
}
|
||||
|
||||
protected override void OnOpenGlRender(GlInterface gl, int fb)
|
||||
{
|
||||
if (_openGLContext is null)
|
||||
{
|
||||
_logger.LogError("OpenGL render called with uninitialized EGL");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
(LibCameraSharp.FrameBuffer, LibCameraSharp.StreamConfiguration)? frame = null;
|
||||
while (frame is null && (DataContext as CameraViewModel)?.CanGrabViewFinderFrame() == true)
|
||||
frame = (DataContext as CameraViewModel)?.GrabViewFinderFrame();
|
||||
if (frame is null)
|
||||
return;
|
||||
var (frameBuffer, streamConfiguration) = frame.Value;
|
||||
|
||||
// 0. Проверяем что полученный фрейм ожидаемый правильный формат
|
||||
var width = streamConfiguration.Size.Width;
|
||||
if (width <= 0)
|
||||
{
|
||||
_logger.LogError("Cannot render frame, stream configuration validation error, expected frame width >= 0, got {}", streamConfiguration.Size.Width);
|
||||
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
return;
|
||||
}
|
||||
var height = streamConfiguration.Size.Height;
|
||||
if (height <= 0)
|
||||
{
|
||||
_logger.LogError("Cannot render frame, stream configuration validation error, expected frame height >= 0, got {}", streamConfiguration.Size.Height);
|
||||
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
return;
|
||||
}
|
||||
if (streamConfiguration.PixelFormat.Fourcc != OpenGLContext.DRM_FORMAT_BGRX8888)
|
||||
{
|
||||
_logger.LogError("Cannot render frame, stream configuration validation error, expected fourcc: 0x{:X}, got 0x{:X}", OpenGLContext.DRM_FORMAT_BGRX8888, streamConfiguration.PixelFormat.Fourcc);
|
||||
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
return;
|
||||
}
|
||||
if (streamConfiguration.PixelFormat.Modifier != 0)
|
||||
{
|
||||
_logger.LogError("Cannot render frame, stream configuration validation error, expected DRM modifier: 0x{:X}, got 0x{:X}", 0, streamConfiguration.PixelFormat.Modifier);
|
||||
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
return;
|
||||
}
|
||||
if (frameBuffer.Planes.Count() != 1)
|
||||
{
|
||||
_logger.LogError("Cannot render frame, planes validation error, expected planes count: 0, got {}", frameBuffer.Planes.Count());
|
||||
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
return;
|
||||
}
|
||||
var plane = frameBuffer.Planes.First();
|
||||
// if (plane.Length != width * height * 4)
|
||||
// {
|
||||
// _logger.LogError("Cannot render frame, plane validation error, expected plane size 0x{:X}, got 0x{:X}", width * height * 4, plane.Length);
|
||||
// (DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
// return;
|
||||
// }
|
||||
var fd = plane.Fd.Get();
|
||||
if (fd <= 0)
|
||||
{
|
||||
_logger.LogError("Cannot render frame, FD validation error, FD >= 0, got 0x{:X}", fd);
|
||||
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
var stretchMode = StretchMode;
|
||||
var tileMode = TileMode;
|
||||
|
||||
// 1. Очищаем экран
|
||||
gl.Disable(GlConsts.GL_SCISSOR_TEST);
|
||||
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height);
|
||||
gl.ClearColor(0f, 0f, 0f, 0f);
|
||||
gl.Clear(GlConsts.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// 2. Создаём EGL изображение из dma-buf
|
||||
IntPtr image;
|
||||
if (_importedDmaBuffers.ContainsKey(fd))
|
||||
{
|
||||
// 2.1. Если EGL изображение для fd уже создано, то берём его из словаря
|
||||
image = _importedDmaBuffers[fd];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2.2. Если EGL изображение для fd не создано, то создаём
|
||||
var attribs = new[]
|
||||
{
|
||||
EglConsts.EGL_WIDTH, (int)width,
|
||||
EglConsts.EGL_HEIGHT, (int)height,
|
||||
OpenGLContext.EGL_LINUX_DRM_FOURCC_EXT, unchecked((int)OpenGLContext.DRM_FORMAT_BGRX8888),
|
||||
OpenGLContext.EGL_DMA_BUF_PLANE0_FD_EXT, fd,
|
||||
OpenGLContext.EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
|
||||
OpenGLContext.EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)(plane.Length / height),
|
||||
EglConsts.EGL_NONE
|
||||
};
|
||||
image = _openGLContext.EglCreateImageKHR.Invoke(_openGLContext.EglDisplay, IntPtr.Zero, OpenGLContext.EGL_LINUX_DMA_BUF_EXT, IntPtr.Zero, attribs);
|
||||
_importedDmaBuffers.Add(fd, image);
|
||||
}
|
||||
|
||||
// 3. Биндим текстуры текстуры
|
||||
gl.ActiveTexture(GlConsts.GL_TEXTURE0);
|
||||
gl.BindTexture(GlConsts.GL_TEXTURE_2D, _openGLContext.Texture);
|
||||
|
||||
// 4. Привязываем EGL изображение к текстуре
|
||||
_openGLContext.GlEGLImageTargetTexture2DOES!(GlConsts.GL_TEXTURE_2D, image);
|
||||
|
||||
// 5. Вычисляем матрицы трансформаций
|
||||
var mvp = ComputeMvp(stretchMode, width, height);
|
||||
var zoom = Matrix4x4.CreateScale(_zoom, _zoom, 1f);
|
||||
var translate = Matrix4x4.CreateTranslation((float)(_panX / Bounds.Width * 2), (float)(_panY / Bounds.Height * 2), 0);
|
||||
mvp *= zoom;
|
||||
mvp *= translate;
|
||||
var mvpArray = new[]
|
||||
{
|
||||
mvp.M11, mvp.M12, mvp.M13, mvp.M14,
|
||||
mvp.M21, mvp.M22, mvp.M23, mvp.M24,
|
||||
mvp.M31, mvp.M32, mvp.M33, mvp.M34,
|
||||
mvp.M41, mvp.M42, mvp.M43, mvp.M44,
|
||||
};
|
||||
unsafe
|
||||
{
|
||||
fixed (float* pMvp = mvpArray)
|
||||
gl.UniformMatrix4fv(_openGLContext.UMvpLocation, 1, false, pMvp);
|
||||
}
|
||||
|
||||
var uv = ComputeUv(tileMode);
|
||||
var uvArray = new[]
|
||||
{
|
||||
uv.M11, uv.M12, 0f,
|
||||
uv.M21, uv.M22, 0f,
|
||||
uv.M31, uv.M32, 1f
|
||||
};
|
||||
unsafe
|
||||
{
|
||||
fixed (float* pUv = uvArray)
|
||||
OpenGLContext.glUniformMatrix3fv(_openGLContext.UUvLocation, 1, false, new IntPtr(pUv));
|
||||
}
|
||||
|
||||
// 6. Запускаем шейдер
|
||||
gl.UseProgram(_openGLContext.Program);
|
||||
OpenGLContext.glUniform1i(_openGLContext.UTexLocation, 0);
|
||||
|
||||
// 7. Рисуем вершины на экране
|
||||
gl.BindVertexArray(_openGLContext.VAO);
|
||||
gl.DrawArrays(OpenGLContext.GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
// 8. Уничтожаем EGL изображение
|
||||
// _openGLContext.EglDestroyImageKHR!(_openGLContext.EglDisplay, image);
|
||||
|
||||
(DataContext as CameraViewModel)?.ReturnViewFinderFrame(frameBuffer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception while OpenGL render");
|
||||
}
|
||||
}
|
||||
private Matrix4x4 ComputeMvp(Stretch stretch, float imageWidth, float imageHeight)
|
||||
{
|
||||
float viewWidth = (float)Bounds.Width;
|
||||
float viewHeight = (float)Bounds.Height;
|
||||
|
||||
float sx = 1f;
|
||||
float sy = 1f;
|
||||
|
||||
float viewAspect = viewWidth / viewHeight;
|
||||
float imgAspect = imageWidth / imageHeight;
|
||||
|
||||
switch (stretch)
|
||||
{
|
||||
case Stretch.None:
|
||||
sx = imageWidth / viewWidth;
|
||||
sy = imageHeight / viewHeight;
|
||||
break;
|
||||
|
||||
case Stretch.Fill:
|
||||
break;
|
||||
|
||||
case Stretch.Uniform:
|
||||
if (imgAspect > viewAspect)
|
||||
sy = viewAspect / imgAspect;
|
||||
else
|
||||
sx = imgAspect / viewAspect;
|
||||
break;
|
||||
|
||||
case Stretch.UniformToFill:
|
||||
if (imgAspect > viewAspect)
|
||||
sx = imgAspect / viewAspect;
|
||||
else
|
||||
sy = viewAspect / imgAspect;
|
||||
break;
|
||||
}
|
||||
|
||||
return Matrix4x4.CreateScale(sx, sy, 1f);
|
||||
}
|
||||
private Matrix3x2 ComputeUv(TileMode mode)
|
||||
{
|
||||
float sx = 1f;
|
||||
float sy = 1f;
|
||||
float ox = 0f;
|
||||
float oy = 0f;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case TileMode.Tile:
|
||||
sx = sy = 2f;
|
||||
break;
|
||||
|
||||
case TileMode.FlipX:
|
||||
sx = -1f;
|
||||
ox = 1f;
|
||||
break;
|
||||
|
||||
case TileMode.FlipY:
|
||||
sy = -1f;
|
||||
oy = 1f;
|
||||
break;
|
||||
|
||||
case TileMode.FlipXY:
|
||||
sx = sy = -1f;
|
||||
ox = oy = 1f;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Matrix3x2(
|
||||
sx, 0,
|
||||
0, sy,
|
||||
ox, oy
|
||||
);
|
||||
}
|
||||
private (float sx, float sy) ComputeScale(Stretch mode, float imageWidth, float imageHeight)
|
||||
{
|
||||
float viewWidth = (float)Bounds.Width;
|
||||
float viewHeight = (float)Bounds.Height;
|
||||
|
||||
float scaleX = viewWidth / imageWidth;
|
||||
float scaleY = viewHeight / imageHeight;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case Stretch.None:
|
||||
return (imageWidth / viewWidth, imageHeight / viewHeight);
|
||||
case Stretch.Fill:
|
||||
return (1f, 1f);
|
||||
case Stretch.Uniform:
|
||||
var sMin = MathF.Min(scaleX, scaleY);
|
||||
return (imageWidth * sMin / viewWidth, imageHeight * sMin / viewHeight);
|
||||
case Stretch.UniformToFill:
|
||||
var sMax = MathF.Max(scaleX, scaleY);
|
||||
return (imageWidth * sMax / viewWidth, imageHeight * sMax / viewHeight);
|
||||
default:
|
||||
return (1f, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="GSS2.UI.Core.Views.IlluminatorControllerView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="using:GSS2.UI.Core.Behaviors"
|
||||
xmlns:converters="using:GSS2.UI.Core.Converters"
|
||||
xmlns:layout="using:Avalonia.Layout"
|
||||
xmlns:local="using:GSS2.UI.Core.Views"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:vm="using:GSS2.UI.Core.ViewModels"
|
||||
x:DataType="vm:IlluminatorControllerViewModel">
|
||||
|
||||
<UserControl.Styles>
|
||||
|
||||
<Style Selector="Slider.horizontal">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Maximum" Value="1" />
|
||||
<Setter Property="Minimum" Value="0" />
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Slider.vertical">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="Maximum" Value="1" />
|
||||
<Setter Property="Minimum" Value="0" />
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
</Style>
|
||||
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid>
|
||||
|
||||
<Grid IsVisible="{Binding Orientation, Converter={x:Static converters:OrientationToBoolConverter.IsHorizontal}}">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="20">
|
||||
|
||||
<Slider Classes="horizontal" Value="{Binding WhiteIntensity}" Maximum="0.03" />
|
||||
<Slider Classes="horizontal" Value="{Binding Uv365Intensity}" />
|
||||
<Slider Classes="horizontal" Value="{Binding Uv254Intensity}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Row="1"
|
||||
Margin="0,10,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
IsChecked="{Binding Enabled}">
|
||||
<TextBlock Text="Включить" FontSize="18"/>
|
||||
</ToggleButton>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid IsVisible="{Binding Orientation, Converter={x:Static converters:OrientationToBoolConverter.IsVertical}}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Spacing="20">
|
||||
|
||||
<Slider Classes="vertical" Value="{Binding WhiteIntensity}" Maximum="0.03" />
|
||||
<Slider Classes="vertical" Value="{Binding Uv365Intensity}" />
|
||||
<Slider Classes="vertical" Value="{Binding Uv254Intensity}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="1"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
IsChecked="{Binding Enabled}">
|
||||
<TextBlock Text="Включить" FontSize="18"/>
|
||||
</ToggleButton>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace GSS2.UI.Core.Views;
|
||||
|
||||
public partial class IlluminatorControllerView : UserControl
|
||||
{
|
||||
public IlluminatorControllerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<lvc:PieChart
|
||||
x:Class="GSS2.UI.Core.Views.ResourceBarView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:vm="using:GSS2.UI.Core.ViewModels"
|
||||
x:DataType="vm:ResourceBarViewModel"
|
||||
Bounds="{Binding Bounds, Mode=OneWayToSource}"
|
||||
Series="{Binding Series}" />
|
||||
@@ -1,11 +0,0 @@
|
||||
using LiveChartsCore.SkiaSharpView.Avalonia;
|
||||
|
||||
namespace GSS2.UI.Core.Views;
|
||||
|
||||
public partial class ResourceBarView : PieChart
|
||||
{
|
||||
public ResourceBarView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<lvc:CartesianChart
|
||||
x:Class="GSS2.UI.Core.Views.ResourceChartView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:vm="using:GSS2.UI.Core.ViewModels"
|
||||
x:DataType="vm:ResourceChartViewModel"
|
||||
Bounds="{Binding Bounds, Mode=OneWayToSource}"
|
||||
EasingFunction="{x:Null}"
|
||||
Series="{Binding Series}"
|
||||
XAxes="{Binding XAxes}"
|
||||
YAxes="{Binding YAxes}"
|
||||
ZoomMode="None" />
|
||||
@@ -1,11 +0,0 @@
|
||||
using LiveChartsCore.SkiaSharpView.Avalonia;
|
||||
|
||||
namespace GSS2.UI.Core.Views;
|
||||
|
||||
public partial class ResourceChartView : CartesianChart
|
||||
{
|
||||
public ResourceChartView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="GSS2.UI.Core.Views.ResourceCpuUsageBarsView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="using:GSS2.UI.Core.Behaviors"
|
||||
xmlns:local="using:GSS2.UI.Core.Views"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:vm="using:GSS2.UI.Core.ViewModels"
|
||||
x:DataType="vm:ResourceCpuUsageBarsViewModel"
|
||||
Bounds="{Binding Bounds, Mode=OneWayToSource}">
|
||||
|
||||
<Grid>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="*" behaviors:GridColumnHideBehavior.IsVisible="{Binding IsCoresColumnVisible}" />
|
||||
<ColumnDefinition Width="*" behaviors:GridColumnHideBehavior.IsVisible="{Binding IsCoresColumnVisible}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<local:ResourceBarView
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
DataContext="{Binding Cpu}" />
|
||||
|
||||
<local:ResourceBarView
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
DataContext="{Binding Cpu0}" />
|
||||
|
||||
<local:ResourceBarView
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
DataContext="{Binding Cpu1}" />
|
||||
|
||||
<local:ResourceBarView
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
DataContext="{Binding Cpu2}" />
|
||||
|
||||
<local:ResourceBarView
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
DataContext="{Binding Cpu3}" />
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace GSS2.UI.Core.Views;
|
||||
|
||||
public partial class ResourceCpuUsageBarsView : UserControl
|
||||
{
|
||||
public ResourceCpuUsageBarsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="GSS2.UI.Core.Views.ResourcesView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="using:GSS2.UI.Core.Behaviors"
|
||||
xmlns:local="using:GSS2.UI.Core.Views"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:vm="using:GSS2.UI.Core.ViewModels"
|
||||
x:DataType="vm:ResourcesViewModel"
|
||||
Bounds="{Binding Bounds, Mode=OneWayToSource}">
|
||||
|
||||
<Grid>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="3*" behaviors:GridColumnHideBehavior.IsVisible="{Binding IsChartsColumnVisible}" />
|
||||
<ColumnDefinition Width="*" behaviors:GridColumnHideBehavior.IsVisible="{Binding IsBarsColumnVisible}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Column="0" RowDefinitions="*,*,*">
|
||||
<local:ResourceChartView Grid.Row="0" DataContext="{Binding CpuUsageChartViewModel}" />
|
||||
<local:ResourceChartView Grid.Row="1" DataContext="{Binding CpuTemperatureChartViewModel}" />
|
||||
<local:ResourceChartView Grid.Row="2" DataContext="{Binding MemoryUsageChartViewModel}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Column="1" RowDefinitions="*,*,*">
|
||||
<local:ResourceCpuUsageBarsView Grid.Row="0" DataContext="{Binding CpuUsageBarsViewModel}" />
|
||||
<local:ResourceBarView Grid.Row="1" DataContext="{Binding CpuTemperatureBarViewModel}" />
|
||||
<local:ResourceBarView Grid.Row="2" DataContext="{Binding MemoryUsageBarViewModel}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace GSS2.UI.Core.Views;
|
||||
|
||||
public partial class ResourcesView : UserControl
|
||||
{
|
||||
public ResourcesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user