Implement BlockMap using Avalonia.

This commit is contained in:
2020-04-17 05:23:17 +01:00
parent fd670972e1
commit 1e976507fd
4 changed files with 689 additions and 366 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -31,124 +31,435 @@
// ****************************************************************************/
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Aaru.Gui.Controls
{
public class BlockMap : ColoredGrid
// TODO: Partially fill clusters
// TODO: React to size changes
// TODO: Optimize block size to viewport
// TODO: Writing one more than it should
public class BlockMap : ItemsControl
{
ulong _sectors;
public uint blocksToRead;
const int _blockSize = 15;
public static readonly StyledProperty<ulong> BlocksProperty =
AvaloniaProperty.Register<Border, ulong>(nameof(Blocks));
public uint sectorsToRead;
public static readonly StyledProperty<IBrush> SuperFastColorProperty =
AvaloniaProperty.Register<Border, IBrush>(nameof(SuperFastColor), Brushes.LightGreen);
public BlockMap()
public static readonly StyledProperty<IBrush> FastColorProperty =
AvaloniaProperty.Register<Border, IBrush>(nameof(FastColor), Brushes.Green);
public static readonly StyledProperty<IBrush> AverageColorProperty =
AvaloniaProperty.Register<Border, IBrush>(nameof(AverageColor), Brushes.DarkGreen);
public static readonly StyledProperty<IBrush> SlowColorProperty =
AvaloniaProperty.Register<Border, IBrush>(nameof(SlowColor), Brushes.Yellow);
public static readonly StyledProperty<IBrush> SuperSlowColorProperty =
AvaloniaProperty.Register<Border, IBrush>(nameof(SuperSlowColor), Brushes.Orange);
public static readonly StyledProperty<IBrush> ProblematicColorProperty =
AvaloniaProperty.Register<Border, IBrush>(nameof(ProblematicColor), Brushes.Red);
public static readonly StyledProperty<double> SuperFastMaxTimeProperty =
AvaloniaProperty.Register<Border, double>(nameof(SuperFastMaxTime), 3);
public static readonly StyledProperty<double> FastMaxTimeProperty =
AvaloniaProperty.Register<Border, double>(nameof(FastMaxTime), 10);
public static readonly StyledProperty<double> AverageMaxTimeProperty =
AvaloniaProperty.Register<Border, double>(nameof(AverageMaxTime), 50);
public static readonly StyledProperty<double> SlowMaxTimeProperty =
AvaloniaProperty.Register<Border, double>(nameof(SlowMaxTime), 150);
public static readonly StyledProperty<double> SuperSlowMaxTimeProperty =
AvaloniaProperty.Register<Border, double>(nameof(SuperSlowMaxTime), 500);
RenderTargetBitmap _bitmap;
ulong _clusterSize;
ulong _maxBlocks;
public double SuperFastMaxTime
{
ColoredSectors = new ObservableCollection<ColoredBlock>();
ColoredSectors.CollectionChanged += OnColoredSectorsChanged;
get => GetValue(SuperFastMaxTimeProperty);
set => SetValue(SuperFastMaxTimeProperty, value);
}
public ulong Sectors
public double FastMaxTime
{
get => _sectors;
set
get => GetValue(FastMaxTimeProperty);
set => SetValue(FastMaxTimeProperty, value);
}
public double AverageMaxTime
{
get => GetValue(AverageMaxTimeProperty);
set => SetValue(AverageMaxTimeProperty, value);
}
public double SlowMaxTime
{
get => GetValue(SlowMaxTimeProperty);
set => SetValue(SlowMaxTimeProperty, value);
}
public double SuperSlowMaxTime
{
get => GetValue(SuperSlowMaxTimeProperty);
set => SetValue(SuperSlowMaxTimeProperty, value);
}
public IBrush SuperFastColor
{
get => GetValue(SuperFastColorProperty);
set => SetValue(SuperFastColorProperty, value);
}
public IBrush FastColor
{
get => GetValue(FastColorProperty);
set => SetValue(FastColorProperty, value);
}
public IBrush AverageColor
{
get => GetValue(AverageColorProperty);
set => SetValue(AverageColorProperty, value);
}
public IBrush SlowColor
{
get => GetValue(SlowColorProperty);
set => SetValue(SlowColorProperty, value);
}
public IBrush SuperSlowColor
{
get => GetValue(SuperSlowColorProperty);
set => SetValue(SuperSlowColorProperty, value);
}
public IBrush ProblematicColor
{
get => GetValue(ProblematicColorProperty);
set => SetValue(ProblematicColorProperty, value);
}
public ulong Blocks
{
get => GetValue(BlocksProperty);
set => SetValue(BlocksProperty, value);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
switch(e.Property.Name)
{
_sectors = value;
CalculateBoundaries();
Invalidate();
case nameof(Blocks):
if(_maxBlocks == 0)
_maxBlocks = (ulong)((Width / _blockSize) * (Height / _blockSize));
if(Blocks > _maxBlocks)
{
_clusterSize = Blocks / _maxBlocks;
if(Blocks % _maxBlocks > 0)
_clusterSize++;
if(Blocks / _clusterSize < _maxBlocks)
{
_maxBlocks = Blocks / _clusterSize;
if(Blocks % _clusterSize > 0)
_maxBlocks++;
}
}
else
{
_clusterSize = 1;
_maxBlocks = Blocks;
}
CreateBitmap();
DrawGrid();
RedrawAll();
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
break;
case nameof(SuperFastMaxTime):
case nameof(FastMaxTime):
case nameof(AverageMaxTime):
case nameof(SlowMaxTime):
case nameof(SuperSlowMaxTime):
case nameof(SuperFastColor):
case nameof(FastColor):
case nameof(AverageColor):
case nameof(SlowColor):
case nameof(SuperSlowColor):
case nameof(ProblematicColor):
CreateBitmap();
DrawGrid();
RedrawAll();
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
break;
}
}
public ulong SectorsPerBlock { get; private set; }
public uint SectorsToRead
public override void Render(DrawingContext context)
{
get => sectorsToRead;
set
if((int?)_bitmap?.Size.Height != (int)Height ||
(int?)_bitmap?.Size.Width != (int)Width)
{
sectorsToRead = value;
blocksToRead = (uint)(sectorsToRead / SectorsPerBlock);
if(sectorsToRead % SectorsPerBlock > 0)
blocksToRead++;
_maxBlocks = (ulong)((Width / _blockSize) * (Height / _blockSize));
CreateBitmap();
}
context.DrawImage(_bitmap, 1, new Rect(0, 0, Width, Height), new Rect(0, 0, Width, Height));
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
base.Render(context);
}
public ObservableCollection<ColoredBlock> ColoredSectors { get; }
void OnColoredSectorsChanged(object sender, NotifyCollectionChangedEventArgs args)
protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch(args.Action)
base.ItemsCollectionChanged(sender, e);
switch(e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach(object item in args.NewItems)
{
if(!(item is ColoredBlock block))
continue;
for(ulong i = 0; i < blocksToRead; i++)
ColoredBlocks.Add(new ColoredBlock((block.Block / SectorsPerBlock) + i, block.Color));
}
break;
case NotifyCollectionChangedAction.Move: break;
case NotifyCollectionChangedAction.Remove:
foreach(object item in args.OldItems)
{
if(!(item is ColoredBlock block))
continue;
for(ulong i = 0; i < blocksToRead; i++)
ColoredBlocks.Remove(ColoredBlocks.FirstOrDefault(t => t.Block == (block.Block /
SectorsPerBlock) + i));
}
break;
case NotifyCollectionChangedAction.Replace:
foreach(object item in args.OldItems)
{
if(!(item is ColoredBlock block))
continue;
{
if(!(e.NewItems is { } items))
throw new ArgumentException("Invalid list of items");
for(ulong i = 0; i < blocksToRead; i++)
ColoredBlocks.Remove(ColoredBlocks.FirstOrDefault(t => t.Block == (block.Block /
SectorsPerBlock) + i));
using IDrawingContextImpl ctxi = _bitmap.CreateDrawingContext(null);
using var ctx = new DrawingContext(ctxi, false);
foreach(object item in items)
{
if(!(item is ValueTuple<ulong, double> block))
throw new ArgumentException("Invalid item in list", nameof(Items));
DrawCluster(block.Item1, block.Item2, false, ctx);
}
foreach(object item in args.NewItems)
{
if(!(item is ColoredBlock block))
continue;
for(ulong i = 0; i < blocksToRead; i++)
ColoredBlocks.Add(new ColoredBlock((block.Block / SectorsPerBlock) + i, block.Color));
}
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
break;
}
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Move:
{
if(!(e.NewItems is { } newItems) ||
!(e.OldItems is { } oldItems))
throw new ArgumentException("Invalid list of items");
using IDrawingContextImpl ctxi = _bitmap.CreateDrawingContext(null);
using var ctx = new DrawingContext(ctxi, false);
foreach(object item in oldItems)
{
if(!(item is ValueTuple<ulong, double> block))
throw new ArgumentException("Invalid item in list", nameof(Items));
DrawCluster(block.Item1, block.Item2, false, ctx);
}
foreach(object item in newItems)
{
if(!(item is ValueTuple<ulong, double> block))
throw new ArgumentException("Invalid item in list", nameof(Items));
DrawCluster(block.Item1, block.Item2, false, ctx);
}
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
break;
}
case NotifyCollectionChangedAction.Reset:
ColoredBlocks.Clear();
CreateBitmap();
DrawGrid();
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
break;
default: throw new ArgumentOutOfRangeException();
}
}
void CalculateBoundaries()
void RedrawAll()
{
SectorsPerBlock = Blocks == 0 ? 0 : _sectors / Blocks;
ColoredBlocks.Clear();
if(SectorsPerBlock > 0)
if(Items is null)
return;
SectorsPerBlock = 1;
using IDrawingContextImpl ctxi = _bitmap.CreateDrawingContext(null);
using var ctx = new DrawingContext(ctxi, false);
for(ulong i = Sectors; i < Blocks; i++)
ColoredBlocks.Add(new ColoredBlock(i, GridColor));
foreach(object item in Items)
{
if(!(item is ValueTuple<ulong, double> block))
throw new ArgumentException("Invalid item in list", nameof(Items));
DrawCluster(block.Item1, block.Item2, false, ctx);
}
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
}
public void Clear() => ColoredSectors.Clear();
void DrawCluster(ulong block, double duration, bool clear = false, DrawingContext ctx = null)
{
if(double.IsNegative(duration) ||
double.IsInfinity(duration))
throw new ArgumentException("Duration cannot be negative or infinite", nameof(duration));
bool newContext = ctx is null;
ulong clustersPerRow = (ulong)Width / _blockSize;
ulong cluster = block / _clusterSize;
ulong row = cluster / clustersPerRow;
ulong column = cluster % clustersPerRow;
ulong x = column * _blockSize;
ulong y = row * _blockSize;
var pen = new Pen(Foreground);
IBrush brush;
if(clear)
brush = Background;
else if(duration < SuperFastMaxTime)
brush = SuperFastColor;
else if(duration >= SuperFastMaxTime &&
duration < FastMaxTime)
brush = FastColor;
else if(duration >= FastMaxTime &&
duration < AverageMaxTime)
brush = AverageColor;
else if(duration >= AverageMaxTime &&
duration < SlowMaxTime)
brush = SlowColor;
else if(duration >= SlowMaxTime &&
duration < SuperSlowMaxTime)
brush = SuperSlowColor;
else if(duration >= SuperSlowMaxTime ||
double.IsNaN(duration))
brush = ProblematicColor;
else
brush = Background;
if(newContext)
{
using IDrawingContextImpl ctxi = _bitmap.CreateDrawingContext(null);
ctx = new DrawingContext(ctxi, false);
}
ctx.FillRectangle(brush, new Rect(x, y, _blockSize, _blockSize));
ctx.DrawRectangle(pen, new Rect(x, y, _blockSize, _blockSize));
if(double.IsNaN(duration))
{
ctx.DrawLine(pen, new Point(x, y), new Point(x + _blockSize, y + _blockSize));
ctx.DrawLine(pen, new Point(x, y + _blockSize), new Point(x + _blockSize, y));
}
if(newContext)
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
}
protected override void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
{
if(e.NewValue != null &&
!(e.NewValue is IList<(ulong, double)>))
{
throw new
ArgumentException("Items must be a IList<(ulong, double)> with ulong being the block and double being the time spent reading it, or NaN for an error.");
}
base.ItemsChanged(e);
CreateBitmap();
DrawGrid();
RedrawAll();
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
}
void CreateBitmap()
{
if(_maxBlocks == 0)
_maxBlocks = (ulong)((Width / _blockSize) * (Height / _blockSize));
_bitmap?.Dispose();
_bitmap = new RenderTargetBitmap(new PixelSize((int)Width, (int)Height), new Vector(96, 96));
using IDrawingContextImpl ctxi = _bitmap.CreateDrawingContext(null);
using var ctx = new DrawingContext(ctxi, false);
ctx.FillRectangle(Background, new Rect(0, 0, Width, Height));
}
void DrawGrid()
{
using IDrawingContextImpl ctxi = _bitmap.CreateDrawingContext(null);
using var ctx = new DrawingContext(ctxi, false);
ulong clustersPerRow = (ulong)Width / _blockSize;
bool allBlocksDrawn = false;
for(ulong y = 0; y < Height && !allBlocksDrawn; y += _blockSize)
{
for(ulong x = 0; x < Width; x += _blockSize)
{
ulong currentBlockValue = ((y * clustersPerRow) / _blockSize) + (x / _blockSize);
if(currentBlockValue >= _maxBlocks ||
currentBlockValue >= Blocks)
{
allBlocksDrawn = true;
break;
}
ctx.DrawRectangle(new Pen(Foreground), new Rect(x, y, _blockSize, _blockSize));
}
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if(Width < 1 ||
Height < 1 ||
double.IsNaN(Width) ||
double.IsNaN(Height))
{
base.OnAttachedToLogicalTree(e);
return;
}
CreateBitmap();
DrawGrid();
base.OnAttachedToLogicalTree(e);
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_bitmap.Dispose();
_bitmap = null;
base.OnDetachedFromLogicalTree(e);
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using System.Reactive;
using System.Threading;
using Aaru.Core;
@@ -19,43 +20,36 @@ namespace Aaru.Gui.ViewModels.Windows
string _a;
string _avgSpeed;
string _b;
ulong _blocksToRead;
string _c;
bool _closeVisible;
string _d;
string _devicePath;
string _e;
string _f;
ScanResults _localResults;
string _maxSpeed;
string _minSpeed;
bool _progress1Visible;
string _progress2Indeterminate;
string _progress2MaxValue;
string _progress2Text;
string _progress2Value;
string _progress2Visible;
bool _progressIndeterminate;
double _progressMaxValue;
string _progressText;
double _progressValue;
bool _progressVisible;
bool _resultsVisible;
MediaScan _scanner;
bool _startVisible;
string _stopEnabled;
bool _stopVisible;
string _totalTime;
string _unreadableSectors;
/*
static readonly Color LightGreen = Color.FromRgb(0x00FF00);
static readonly Color Green = Color.FromRgb(0x006400);
static readonly Color DarkGreen = Color.FromRgb(0x003200);
static readonly Color Yellow = Color.FromRgb(0xFFA500);
static readonly Color Orange = Color.FromRgb(0xFF4500);
static readonly Color Red = Color.FromRgb(0x800000);
static Color LightRed = Color.FromRgb(0xFF0000);
*/
ulong _blocks;
ulong _blocksToRead;
string _c;
bool _closeVisible;
string _d;
string _devicePath;
string _e;
string _f;
ScanResults _localResults;
string _maxSpeed;
string _minSpeed;
bool _progress1Visible;
string _progress2Indeterminate;
string _progress2MaxValue;
string _progress2Text;
string _progress2Value;
string _progress2Visible;
bool _progressIndeterminate;
double _progressMaxValue;
string _progressText;
double _progressValue;
bool _progressVisible;
bool _resultsVisible;
MediaScan _scanner;
bool _startVisible;
string _stopEnabled;
bool _stopVisible;
string _totalTime;
string _unreadableSectors;
public MediaScanViewModel(string devicePath, DeviceInfo deviceInfo, Window view, ScsiInfo scsiInfo = null)
{
@@ -67,6 +61,7 @@ namespace Aaru.Gui.ViewModels.Windows
StopCommand = ReactiveCommand.Create(ExecuteStopCommand);
StartVisible = true;
CloseVisible = true;
BlockMapList = new ObservableCollection<(ulong block, double duration)>();
/*
lineChart.AbsoluteMargins = true;
@@ -80,6 +75,23 @@ namespace Aaru.Gui.ViewModels.Windows
lineChart.LineColor = Colors.Yellow;
*/
}
/*
static readonly Color LightGreen = Color.FromRgb(0x00FF00);
static readonly Color Green = Color.FromRgb(0x006400);
static readonly Color DarkGreen = Color.FromRgb(0x003200);
static readonly Color Yellow = Color.FromRgb(0xFFA500);
static readonly Color Orange = Color.FromRgb(0xFF4500);
static readonly Color Red = Color.FromRgb(0x800000);
static Color LightRed = Color.FromRgb(0xFF0000);
*/
public ObservableCollection<(ulong block, double duration)> BlockMapList { get; }
public ulong Blocks
{
get => _blocks;
set => this.RaiseAndSetIfChanged(ref _blocks, value);
}
public string A
{
@@ -331,10 +343,10 @@ namespace Aaru.Gui.ViewModels.Windows
async void InitBlockMap(ulong blocks, ulong blocksize, ulong blockstoread, ushort currentProfile) =>
await Dispatcher.UIThread.InvokeAsync(() =>
{
/* TODO: BlockMap
blockMap.Sectors = blocks;
blockMap.SectorsToRead = (uint)blockstoread;
blocksToRead = blockstoread;
Blocks = blocks / blockstoread;
_blocksToRead = blockstoread;
/* TODO: Chart
lineChart.MinX = 0;
lineChart.MinY = 0;
@@ -488,6 +500,7 @@ namespace Aaru.Gui.ViewModels.Windows
{
_localResults.Errored += _blocksToRead;
UnreadableSectors = $"{_localResults.Errored} sectors could not be read.";
BlockMapList.Add((sector / _blocksToRead, double.NaN));
/* TODO: Blockmap
blockMap.ColoredSectors.Add(new ColoredBlock(sector, LightGreen));
*/
@@ -495,6 +508,8 @@ namespace Aaru.Gui.ViewModels.Windows
async void OnScanTime(ulong sector, double duration) => await Dispatcher.UIThread.InvokeAsync(() =>
{
BlockMapList.Add((sector / _blocksToRead, duration));
if(duration < 3)
{
_localResults.A += _blocksToRead;

View File

@@ -1,9 +1,9 @@
<Window 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:windows="clr-namespace:Aaru.Gui.ViewModels.Windows" mc:Ignorable="d" d:DesignWidth="800"
d:DesignHeight="450" x:Class="Aaru.Gui.Views.Windows.MediaScan" Icon="/Assets/aaru-logo.png"
Title="{Binding Title}">
xmlns:windows="clr-namespace:Aaru.Gui.ViewModels.Windows" xmlns:controls="clr-namespace:Aaru.Gui.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Aaru.Gui.Views.Windows.MediaScan"
Icon="/Assets/aaru-logo.png" Title="{Binding Title}">
<Design.DataContext>
<windows:MediaScanViewModel />
</Design.DataContext>
@@ -27,7 +27,7 @@
<TabItem.Header>
<TextBlock Text="Graph" />
</TabItem.Header>
<!-- TODO: Graph -->
<controls:BlockMap Width="750" Height="400" Blocks="{Binding Blocks}" Items="{Binding BlockMapList}" />
</TabItem>
<TabItem>
<TabItem.Header>