mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
[GUI] Complete rework of block map.
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
x:Class="Aaru.Gui.Controls.BlockMap">
|
x:Class="Aaru.Gui.Controls.BlockMap">
|
||||||
<Canvas Name="BlockCanvas"
|
<!-- Replaced Canvas with Image that displays a generated bitmap -->
|
||||||
Background="Transparent" />
|
<Image x:Name="BlockImage"
|
||||||
|
Stretch="None" />
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -41,8 +41,11 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
|
||||||
namespace Aaru.Gui.Controls;
|
namespace Aaru.Gui.Controls;
|
||||||
|
|
||||||
@@ -60,17 +63,20 @@ public partial class BlockMap : UserControl
|
|||||||
|
|
||||||
public static readonly StyledProperty<uint> ScanBlockSizeProperty =
|
public static readonly StyledProperty<uint> ScanBlockSizeProperty =
|
||||||
AvaloniaProperty.Register<BlockMap, uint>(nameof(ScanBlockSize), 1u);
|
AvaloniaProperty.Register<BlockMap, uint>(nameof(ScanBlockSize), 1u);
|
||||||
int _blocksPerRow;
|
readonly Image _image;
|
||||||
|
|
||||||
|
WriteableBitmap _bitmap;
|
||||||
|
int _blocksPerRow;
|
||||||
|
int _rows;
|
||||||
|
|
||||||
readonly Canvas _canvas;
|
|
||||||
uint _scanBlockSize = 1;
|
uint _scanBlockSize = 1;
|
||||||
ObservableCollection<(ulong startingSector, double duration)> _sectorData;
|
ObservableCollection<(ulong startingSector, double duration)> _sectorData;
|
||||||
int _totalBlocksDrawn;
|
|
||||||
|
|
||||||
public BlockMap()
|
public BlockMap()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_canvas = this.FindControl<Canvas>("BlockCanvas");
|
_image = this.FindControl<Image>("BlockImage");
|
||||||
|
_image.PointerMoved += OnPointerMoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<(ulong startingSector, double duration)> SectorData
|
public ObservableCollection<(ulong startingSector, double duration)> SectorData
|
||||||
@@ -118,76 +124,196 @@ public partial class BlockMap : UserControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSectorDataChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if(e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
|
|
||||||
{
|
|
||||||
// Incremental draw for added items
|
|
||||||
DrawNewBlocks(e.NewStartingIndex, e.NewItems.Count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Full redraw for other operations
|
|
||||||
RedrawAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CalculateBlocksPerRow()
|
private void CalculateBlocksPerRow()
|
||||||
{
|
{
|
||||||
if(Bounds.Width <= 0) return;
|
if(Bounds.Width <= 0) return;
|
||||||
|
|
||||||
var availableWidth = (int)Bounds.Width;
|
|
||||||
int blockWithSpacing = BlockSize + BlockSpacing;
|
int blockWithSpacing = BlockSize + BlockSpacing;
|
||||||
_blocksPerRow = Math.Max(1, availableWidth / blockWithSpacing);
|
_blocksPerRow = Math.Max(1, (int)Bounds.Width / blockWithSpacing);
|
||||||
|
_rows = _sectorData == null ? 0 : (_sectorData.Count + _blocksPerRow - 1) / _blocksPerRow;
|
||||||
|
EnsureBitmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnsureBitmap()
|
||||||
|
{
|
||||||
|
if(_blocksPerRow <= 0 || _rows <= 0) return;
|
||||||
|
int blockWithSpacing = BlockSize + BlockSpacing;
|
||||||
|
int width = _blocksPerRow * blockWithSpacing;
|
||||||
|
int height = _rows * blockWithSpacing;
|
||||||
|
|
||||||
|
if(_bitmap == null || _bitmap.PixelSize.Width != width || _bitmap.PixelSize.Height != height)
|
||||||
|
{
|
||||||
|
_bitmap?.Dispose();
|
||||||
|
|
||||||
|
_bitmap = new WriteableBitmap(new PixelSize(width, height),
|
||||||
|
new Vector(96, 96),
|
||||||
|
PixelFormat.Bgra8888,
|
||||||
|
AlphaFormat.Premul);
|
||||||
|
|
||||||
|
if(_image != null)
|
||||||
|
{
|
||||||
|
_image.Source = _bitmap;
|
||||||
|
_image.Width = width;
|
||||||
|
_image.Height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RedrawAll()
|
private void RedrawAll()
|
||||||
{
|
{
|
||||||
if(_canvas == null || _sectorData == null || _sectorData.Count == 0) return;
|
if(_sectorData == null || _sectorData.Count == 0 || _blocksPerRow == 0) return;
|
||||||
|
|
||||||
_canvas.Children.Clear();
|
|
||||||
CalculateBlocksPerRow();
|
CalculateBlocksPerRow();
|
||||||
_totalBlocksDrawn = 0;
|
|
||||||
|
|
||||||
DrawNewBlocks(0, _sectorData.Count);
|
if(_bitmap == null) return;
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawNewBlocks(int startIndex, int count)
|
using(ILockedFramebuffer fb = _bitmap.Lock())
|
||||||
{
|
|
||||||
if(_canvas == null || _sectorData == null || _blocksPerRow == 0) return;
|
|
||||||
|
|
||||||
int blockWithSpacing = BlockSize + BlockSpacing;
|
|
||||||
|
|
||||||
for(int i = startIndex; i < startIndex + count && i < _sectorData.Count; i++)
|
|
||||||
{
|
{
|
||||||
(ulong startingSector, double duration) = _sectorData[i];
|
unsafe
|
||||||
Color color = GetColorForDuration(duration);
|
|
||||||
|
|
||||||
// Calculate position in grid
|
|
||||||
int blockIndex = _totalBlocksDrawn;
|
|
||||||
int row = blockIndex / _blocksPerRow;
|
|
||||||
int col = blockIndex % _blocksPerRow;
|
|
||||||
|
|
||||||
// Create and position rectangle
|
|
||||||
var rect = new Border
|
|
||||||
{
|
{
|
||||||
Width = BlockSize,
|
var data = new Span<byte>((void*)fb.Address, fb.Size.Height * fb.RowBytes);
|
||||||
Height = BlockSize,
|
data.Clear();
|
||||||
Background = new SolidColorBrush(color),
|
|
||||||
BorderBrush = Brushes.Transparent,
|
|
||||||
BorderThickness = new Thickness(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
Canvas.SetLeft(rect, col * blockWithSpacing);
|
int blockWithSpacing = BlockSize + BlockSpacing;
|
||||||
Canvas.SetTop(rect, row * blockWithSpacing);
|
|
||||||
|
|
||||||
_canvas.Children.Add(rect);
|
for(var i = 0; i < _sectorData.Count; i++)
|
||||||
_totalBlocksDrawn++;
|
{
|
||||||
|
(ulong startSector, double duration) = _sectorData[i];
|
||||||
|
Color color = GetColorForDuration(duration);
|
||||||
|
|
||||||
|
int row = i / _blocksPerRow;
|
||||||
|
int col = i % _blocksPerRow;
|
||||||
|
DrawBlock(fb, col * blockWithSpacing, row * blockWithSpacing, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update canvas height based on rows needed
|
_image.InvalidateVisual();
|
||||||
int totalRows = (_totalBlocksDrawn + _blocksPerRow - 1) / _blocksPerRow;
|
}
|
||||||
_canvas.Height = totalRows * blockWithSpacing;
|
|
||||||
|
void DrawBlock(ILockedFramebuffer fb, int x, int y, Color color)
|
||||||
|
{
|
||||||
|
int stride = fb.RowBytes;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
var basePtr = (byte*)fb.Address;
|
||||||
|
|
||||||
|
for(var dy = 0; dy < BlockSize; dy++)
|
||||||
|
{
|
||||||
|
byte* rowPtr = basePtr + (y + dy) * stride + x * 4;
|
||||||
|
|
||||||
|
for(var dx = 0; dx < BlockSize; dx++)
|
||||||
|
{
|
||||||
|
rowPtr[0] = color.B;
|
||||||
|
rowPtr[1] = color.G;
|
||||||
|
rowPtr[2] = color.R;
|
||||||
|
rowPtr[3] = 255;
|
||||||
|
rowPtr += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSectorDataChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if(_sectorData == null) return;
|
||||||
|
|
||||||
|
if(e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && e.NewItems.Count > 0)
|
||||||
|
{
|
||||||
|
// If not laid out yet, defer to full redraw
|
||||||
|
if(_blocksPerRow == 0)
|
||||||
|
{
|
||||||
|
RedrawAll();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if rows changed (only depends on count now)
|
||||||
|
int newRows = (_sectorData.Count + _blocksPerRow - 1) / _blocksPerRow;
|
||||||
|
|
||||||
|
if(newRows != _rows)
|
||||||
|
{
|
||||||
|
_rows = newRows;
|
||||||
|
EnsureBitmap();
|
||||||
|
RedrawAll();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_bitmap == null) EnsureBitmap();
|
||||||
|
|
||||||
|
if(_bitmap == null) return;
|
||||||
|
|
||||||
|
using(ILockedFramebuffer fb = _bitmap.Lock())
|
||||||
|
{
|
||||||
|
int blockWithSpacing = BlockSize + BlockSpacing;
|
||||||
|
int startIndex = e.NewStartingIndex;
|
||||||
|
int count = e.NewItems.Count;
|
||||||
|
|
||||||
|
for(var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
int dataIndex = startIndex + i;
|
||||||
|
|
||||||
|
if(dataIndex >= _sectorData.Count) break;
|
||||||
|
(ulong startSector, double duration) = _sectorData[dataIndex];
|
||||||
|
Color color = GetColorForDuration(duration);
|
||||||
|
int row = dataIndex / _blocksPerRow;
|
||||||
|
int col = dataIndex % _blocksPerRow;
|
||||||
|
DrawBlock(fb, col * blockWithSpacing, row * blockWithSpacing, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_image.InvalidateVisual();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
RedrawAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnPointerMoved(object sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if(_sectorData == null || _sectorData.Count == 0 || _blocksPerRow == 0)
|
||||||
|
{
|
||||||
|
ToolTip.SetTip(_image, null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point p = e.GetPosition(_image);
|
||||||
|
int blockWithSpacing = BlockSize + BlockSpacing;
|
||||||
|
|
||||||
|
if(p.X < 0 || p.Y < 0)
|
||||||
|
{
|
||||||
|
ToolTip.SetTip(_image, null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var col = (int)(p.X / blockWithSpacing);
|
||||||
|
var row = (int)(p.Y / blockWithSpacing);
|
||||||
|
|
||||||
|
if(col < 0 || row < 0)
|
||||||
|
{
|
||||||
|
ToolTip.SetTip(_image, null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = row * _blocksPerRow + col;
|
||||||
|
|
||||||
|
if(index < 0 || index >= _sectorData.Count)
|
||||||
|
{
|
||||||
|
ToolTip.SetTip(_image, null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(ulong startSector, double duration) = _sectorData[index];
|
||||||
|
ulong endSector = startSector + (_scanBlockSize > 0 ? _scanBlockSize - 1u : 0u);
|
||||||
|
|
||||||
|
string tooltipText = _scanBlockSize > 1
|
||||||
|
? $"Sectors {startSector} - {endSector}\nBlock size: {_scanBlockSize} sectors\nDuration: {duration:F2} ms"
|
||||||
|
: $"Sector {startSector}\nDuration: {duration:F2} ms";
|
||||||
|
|
||||||
|
ToolTip.SetTip(_image, tooltipText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color GetColorForDuration(double duration)
|
private Color GetColorForDuration(double duration)
|
||||||
|
|||||||
Reference in New Issue
Block a user