mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
520 lines
16 KiB
C#
520 lines
16 KiB
C#
using Avalonia;
|
|
using Avalonia.Media;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Rendering.SceneGraph;
|
|
using Avalonia.Skia;
|
|
using Mesen.Config;
|
|
using Mesen.Utilities;
|
|
using SkiaSharp;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace Mesen.Debugger.Controls
|
|
{
|
|
public partial class HexEditor
|
|
{
|
|
class HexViewDrawOperation : ICustomDrawOperation
|
|
{
|
|
private HexEditor _he;
|
|
private List<ByteInfo> _dataToDraw;
|
|
private HashSet<Color> _fgColors;
|
|
private Size _letterSize;
|
|
private int _bytesPerRow;
|
|
private bool _showStringView;
|
|
private bool _inStringView;
|
|
private double _rowHeight;
|
|
private string _hexFormat;
|
|
private string _fontFamily;
|
|
private float _fontSize;
|
|
private double _stringViewPosition;
|
|
private IHexEditorDataProvider _dataProvider;
|
|
private Dictionary<Color, SKPaint> _skFillPaints = new Dictionary<Color, SKPaint>();
|
|
private Dictionary<Color, SKPaint> _skBorderPaints = new Dictionary<Color, SKPaint>();
|
|
private Color _selectedColorOther = ColorHelper.GetColor(Colors.LightBlue);
|
|
private Color _selectedColor = ColorHelper.GetColor(Colors.LightSkyBlue);
|
|
private bool _highDensityMode;
|
|
|
|
private SKFontEdging _skiaEdging;
|
|
private bool _skiaSubpixelSmoothing;
|
|
|
|
public HexViewDrawOperation(HexEditor he, List<ByteInfo> dataToDraw, HashSet<Color> fgColors, FontAntialiasing fontAntialiasing)
|
|
{
|
|
_he = he;
|
|
Bounds = _he.Bounds;
|
|
_fontFamily = _he.FontFamily.Name;
|
|
_fontSize = (float)_he.FontSize;
|
|
_bytesPerRow = _he.BytesPerRow;
|
|
_hexFormat = _he.HexFormat;
|
|
_rowHeight = _he.RowHeight;
|
|
_dataProvider = _he.DataProvider;
|
|
_dataToDraw = dataToDraw;
|
|
_fgColors = fgColors;
|
|
_letterSize = _he.LetterSize;
|
|
_showStringView = _he.ShowStringView;
|
|
_inStringView = _he._inStringView;
|
|
_stringViewPosition = _he.RowWidth + _he.StringViewMargin;
|
|
_highDensityMode = _he.HighDensityMode;
|
|
|
|
_skiaEdging = fontAntialiasing switch {
|
|
FontAntialiasing.Disabled => SKFontEdging.Alias,
|
|
FontAntialiasing.Antialias => SKFontEdging.Antialias,
|
|
FontAntialiasing.SubPixelAntialias or _ => SKFontEdging.SubpixelAntialias
|
|
};
|
|
_skiaSubpixelSmoothing = fontAntialiasing == FontAntialiasing.SubPixelAntialias;
|
|
|
|
foreach(ByteInfo byteInfo in dataToDraw) {
|
|
if(!_skFillPaints.ContainsKey(byteInfo.BackColor)) {
|
|
_skFillPaints[byteInfo.BackColor] = new SKPaint() { Color = new SKColor(ColorHelper.GetColor(byteInfo.BackColor).ToUInt32()) };
|
|
}
|
|
if(!_skBorderPaints.ContainsKey(byteInfo.BorderColor)) {
|
|
_skBorderPaints[byteInfo.BorderColor] = new SKPaint() { Style = SKPaintStyle.Stroke, Color = new SKColor(ColorHelper.GetColor(byteInfo.BorderColor).ToUInt32()) };
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetFontProperties(SKFont font)
|
|
{
|
|
font.Edging = _skiaEdging;
|
|
font.Subpixel = _skiaSubpixelSmoothing;
|
|
|
|
//Fixes layout issues (on Linux)
|
|
font.Hinting = SKFontHinting.Full;
|
|
font.LinearMetrics = true;
|
|
}
|
|
|
|
public Rect Bounds { get; private set; }
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
public bool Equals(ICustomDrawOperation? other) => false;
|
|
public bool HitTest(Point p) => false;
|
|
|
|
public void Render(ImmediateDrawingContext context)
|
|
{
|
|
var leaseFeature = context.PlatformImpl.GetFeature<ISkiaSharpApiLeaseFeature>();
|
|
using var lease = leaseFeature?.Lease();
|
|
var canvas = lease?.SkCanvas;
|
|
if(canvas == null) {
|
|
//context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
|
|
} else {
|
|
canvas.Save();
|
|
|
|
DrawBackground(canvas);
|
|
|
|
foreach(Color color in _fgColors) {
|
|
DrawHexView(canvas, color);
|
|
}
|
|
|
|
if(_showStringView) {
|
|
canvas.Translate((float)_stringViewPosition, 0);
|
|
PrepareStringView();
|
|
foreach(Color color in _fgColors) {
|
|
DrawStringView(canvas, color);
|
|
}
|
|
}
|
|
|
|
canvas.Restore();
|
|
}
|
|
}
|
|
|
|
private void DrawHexView(SKCanvas canvas, Color color)
|
|
{
|
|
SKPaint paint = new SKPaint();
|
|
paint.Color = new SKColor(ColorHelper.GetColor(color).ToUInt32());
|
|
|
|
SKTypeface typeface = SKTypeface.FromFamilyName(_fontFamily);
|
|
SKFont font = new SKFont(typeface, _fontSize);
|
|
SetFontProperties(font);
|
|
|
|
using var builder = new SKTextBlobBuilder();
|
|
|
|
int pos = 0;
|
|
double drawOffsetY = _highDensityMode ? -_rowHeight * 0.1 : -_rowHeight * 0.2;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
int row = 0;
|
|
while(pos < _dataToDraw.Count) {
|
|
for(int i = 0; i < _bytesPerRow; i++) {
|
|
if(pos + i >= _dataToDraw.Count) {
|
|
break;
|
|
}
|
|
ByteInfo byteInfo = _dataToDraw[pos + i];
|
|
if(byteInfo.ForeColor == color) {
|
|
sb.Append(byteInfo.Value.ToString(_hexFormat));
|
|
} else {
|
|
sb.Append(" ");
|
|
}
|
|
sb.Append(' ');
|
|
}
|
|
pos += _bytesPerRow;
|
|
|
|
string rowText = sb.ToString();
|
|
int count = font.CountGlyphs(rowText);
|
|
var buffer = builder.AllocateRun(font, count, 0, (float)(row * _rowHeight + drawOffsetY));
|
|
font.GetGlyphs(rowText, buffer.GetGlyphSpan());
|
|
row++;
|
|
sb.Clear();
|
|
}
|
|
|
|
SKTextBlob? textToDraw = builder.Build();
|
|
if(textToDraw != null) {
|
|
canvas.DrawText(textToDraw, 0, (float)_rowHeight, paint);
|
|
}
|
|
}
|
|
|
|
private void PrepareStringView()
|
|
{
|
|
SKFont altFont = new SKFont(SKFontManager.Default.MatchCharacter('あ'), _fontSize);
|
|
SetFontProperties(altFont);
|
|
|
|
int pos = 0;
|
|
|
|
using var measureText = new SKTextBlobBuilder();
|
|
float[] startPositionByByte = new float[_dataToDraw.Count];
|
|
float[] endPositionByByte = new float[_dataToDraw.Count];
|
|
while(pos < _dataToDraw.Count) {
|
|
double xPos = 0;
|
|
int i;
|
|
int gap = pos % _bytesPerRow;
|
|
pos -= gap;
|
|
for(i = gap; i < _bytesPerRow; i++) {
|
|
if(pos + i >= _dataToDraw.Count) {
|
|
break;
|
|
}
|
|
|
|
int index = pos + i;
|
|
ByteInfo byteInfo = _dataToDraw[index];
|
|
UInt64 tblKeyValue = (UInt64)byteInfo.Value;
|
|
for(int j = 1; j < 8; j++) {
|
|
if(index + j < _dataToDraw.Count) {
|
|
tblKeyValue += (UInt64)_dataToDraw[index + j].Value << (8 * j);
|
|
}
|
|
}
|
|
|
|
string str = _dataProvider.ConvertValueToString(tblKeyValue, out int keyLength);
|
|
byteInfo.StringValue = str;
|
|
byteInfo.StringValueKeyLength = keyLength;
|
|
|
|
int codepoint = Char.ConvertToUtf32(str, 0);
|
|
if(codepoint > 0x024F) {
|
|
SKRunBuffer measureBuffer = measureText.AllocateRun(altFont, altFont.CountGlyphs(str), 0, 0);
|
|
byteInfo.UseAltFont = true;
|
|
altFont.GetGlyphs(str, measureBuffer.GetGlyphSpan());
|
|
startPositionByByte[index] = (float)xPos;
|
|
xPos += altFont.MeasureText(measureBuffer.GetGlyphSpan());
|
|
endPositionByByte[index] = (float)xPos;
|
|
} else {
|
|
startPositionByByte[index] = (float)xPos;
|
|
xPos += _letterSize.Width * str.Length;
|
|
endPositionByByte[index] = (float)xPos;
|
|
}
|
|
|
|
for(int j = 1; j < byteInfo.StringValueKeyLength && index + j < startPositionByByte.Length; j++) {
|
|
startPositionByByte[index + j] = startPositionByByte[index];
|
|
endPositionByByte[index + j] = endPositionByByte[index];
|
|
}
|
|
|
|
i += (byteInfo.StringValueKeyLength - 1);
|
|
_dataToDraw[index] = byteInfo;
|
|
}
|
|
pos += i;
|
|
}
|
|
|
|
_he._startPositionByByte = startPositionByByte;
|
|
_he._endPositionByByte = endPositionByByte;
|
|
}
|
|
|
|
private void DrawStringView(SKCanvas canvas, Color color)
|
|
{
|
|
SKPaint paint = new SKPaint();
|
|
paint.Color = new SKColor(ColorHelper.GetColor(color).ToUInt32());
|
|
|
|
SKTypeface typeface = SKTypeface.FromFamilyName(_fontFamily);
|
|
SKFont monoFont = new SKFont(typeface, _fontSize);
|
|
SetFontProperties(monoFont);
|
|
|
|
SKFont altFont = new SKFont(SKFontManager.Default.MatchCharacter('あ'), _fontSize);
|
|
SetFontProperties(altFont);
|
|
|
|
using var builder = new SKTextBlobBuilder();
|
|
|
|
int pos = 0;
|
|
int row = 0;
|
|
double drawOffsetY = _highDensityMode ? -_rowHeight * 0.1 : -_rowHeight * 0.2;
|
|
|
|
Color selectedColor = _inStringView ? _selectedColor : _selectedColorOther;
|
|
SKPaint selectedPaint = new SKPaint() { Color = new SKColor(selectedColor.R, selectedColor.G, selectedColor.B, 255) };
|
|
|
|
SKRect GetRect(int i) => new SKRect(
|
|
(float)_he._startPositionByByte[i],
|
|
(float)(row * _rowHeight),
|
|
(float)_he._endPositionByByte[i],
|
|
(float)((row + 1) * _rowHeight)
|
|
);
|
|
|
|
while(pos < _dataToDraw.Count) {
|
|
int i;
|
|
int gap = pos % _bytesPerRow;
|
|
pos -= gap;
|
|
for(i = gap; i < _bytesPerRow; i++) {
|
|
if(pos + i >= _dataToDraw.Count) {
|
|
break;
|
|
}
|
|
|
|
ByteInfo byteInfo = _dataToDraw[pos + i];
|
|
|
|
if(byteInfo.ForeColor == color) {
|
|
SKFont currentFont = byteInfo.UseAltFont ? altFont : monoFont;
|
|
|
|
SKRect rect = GetRect(pos + i);
|
|
if(byteInfo.BackColor != Colors.Transparent) {
|
|
canvas.DrawRect(rect, _skFillPaints[byteInfo.BackColor]);
|
|
}
|
|
if(byteInfo.Selected) {
|
|
canvas.DrawRect(rect, selectedPaint);
|
|
}
|
|
if(byteInfo.BorderColor != Colors.Transparent) {
|
|
rect.Inflate(0, -1);
|
|
rect.Offset(0, -0.5f);
|
|
canvas.DrawRect(rect, _skBorderPaints[byteInfo.BorderColor]);
|
|
}
|
|
|
|
int count = currentFont.CountGlyphs(byteInfo.StringValue);
|
|
var buffer = builder.AllocateRun(currentFont, count, _he._startPositionByByte[pos + i], (float)(row * _rowHeight + drawOffsetY));
|
|
currentFont.GetGlyphs(byteInfo.StringValue, buffer.GetGlyphSpan());
|
|
}
|
|
|
|
i += (byteInfo.StringValueKeyLength - 1);
|
|
}
|
|
|
|
pos += i;
|
|
row++;
|
|
}
|
|
|
|
SKTextBlob? textToDraw = builder.Build();
|
|
if(textToDraw != null) {
|
|
canvas.DrawText(textToDraw, 0, (float)_rowHeight, paint);
|
|
}
|
|
}
|
|
|
|
private void DrawBackground(SKCanvas canvas)
|
|
{
|
|
int pos = 0;
|
|
int row = 0;
|
|
|
|
Color selectedColor = _inStringView ? _selectedColorOther : _selectedColor;
|
|
SKPaint selectedPaint = new SKPaint() { Color = new SKColor(selectedColor.R, selectedColor.G, selectedColor.B, 255) };
|
|
|
|
SKRect GetRect(int start, int end) => new SKRect(
|
|
(float)(start * 3 * _letterSize.Width),
|
|
(float)(row * _rowHeight),
|
|
(float)((end * 3 - 1) * _letterSize.Width),
|
|
(float)((row + 1) * _rowHeight)
|
|
);
|
|
|
|
while(pos < _dataToDraw.Count) {
|
|
int bgStartPos = -1;
|
|
int borderStartPos = -1;
|
|
int selectedStartPos = -1;
|
|
Color bgColor = Colors.Transparent;
|
|
Color borderColor = Colors.Transparent;
|
|
bool selected = false;
|
|
|
|
for(int i = 0; i < _bytesPerRow; i++) {
|
|
if(pos + i >= _dataToDraw.Count) {
|
|
break;
|
|
}
|
|
|
|
ByteInfo byteInfo = _dataToDraw[pos + i];
|
|
|
|
if(byteInfo.BackColor != bgColor) {
|
|
if(bgColor != Colors.Transparent && bgStartPos >= 0) {
|
|
canvas.DrawRect(GetRect(bgStartPos, i), _skFillPaints[bgColor]);
|
|
bgStartPos = -1;
|
|
}
|
|
if(byteInfo.BackColor != Colors.Transparent) {
|
|
bgStartPos = i;
|
|
}
|
|
bgColor = byteInfo.BackColor;
|
|
}
|
|
}
|
|
|
|
if(bgStartPos >= 0) {
|
|
canvas.DrawRect(GetRect(bgStartPos, _bytesPerRow), _skFillPaints[bgColor]);
|
|
}
|
|
|
|
for(int i = 0; i < _bytesPerRow; i++) {
|
|
if(pos + i >= _dataToDraw.Count) {
|
|
break;
|
|
}
|
|
|
|
ByteInfo byteInfo = _dataToDraw[pos + i];
|
|
|
|
if(selected != byteInfo.Selected) {
|
|
if(selectedStartPos >= 0 && selected) {
|
|
canvas.DrawRect(GetRect(selectedStartPos, i), selectedPaint);
|
|
selectedStartPos = -1;
|
|
}
|
|
if(byteInfo.Selected) {
|
|
selectedStartPos = i;
|
|
}
|
|
selected = byteInfo.Selected;
|
|
}
|
|
}
|
|
|
|
if(selectedStartPos >= 0) {
|
|
canvas.DrawRect(GetRect(selectedStartPos, _bytesPerRow), selectedPaint);
|
|
}
|
|
|
|
for(int i = 0; i < _bytesPerRow; i++) {
|
|
if(pos + i >= _dataToDraw.Count) {
|
|
break;
|
|
}
|
|
|
|
ByteInfo byteInfo = _dataToDraw[pos + i];
|
|
|
|
if(byteInfo.BorderColor != borderColor) {
|
|
if(borderColor != Colors.Transparent && borderStartPos >= 0) {
|
|
SKRect rect = GetRect(borderStartPos, i);
|
|
rect.Inflate(0, -1);
|
|
rect.Offset(0, -0.5f);
|
|
canvas.DrawRect(rect, _skBorderPaints[borderColor]);
|
|
borderStartPos = -1;
|
|
}
|
|
if(byteInfo.BorderColor != Colors.Transparent) {
|
|
borderStartPos = i;
|
|
}
|
|
borderColor = byteInfo.BorderColor;
|
|
}
|
|
|
|
}
|
|
|
|
if(borderStartPos >= 0) {
|
|
SKRect rect = GetRect(borderStartPos, _bytesPerRow);
|
|
rect.Inflate(0, -1);
|
|
rect.Offset(0, -0.5f);
|
|
canvas.DrawRect(rect, _skBorderPaints[borderColor]);
|
|
}
|
|
|
|
pos += _bytesPerRow;
|
|
row++;
|
|
}
|
|
}
|
|
}
|
|
|
|
class HexViewDrawRowHeaderOperation : ICustomDrawOperation
|
|
{
|
|
private HexEditor _he;
|
|
private Size _letterSize;
|
|
private int _bytesPerRow;
|
|
private double _rowHeight;
|
|
private string _fontFamily;
|
|
private float _fontSize;
|
|
private IHexEditorDataProvider _dataProvider;
|
|
private double _headerCharLength;
|
|
private double _rowHeaderWidth;
|
|
private double _columnHeaderHeight;
|
|
private Color _headerForeground;
|
|
private int _firstByte;
|
|
private bool _highDensityMode;
|
|
|
|
private SKFontEdging _skiaEdging;
|
|
private bool _skiaSubpixelSmoothing;
|
|
|
|
public HexViewDrawRowHeaderOperation(HexEditor he, FontAntialiasing fontAntialiasing)
|
|
{
|
|
_he = he;
|
|
Bounds = _he.Bounds;
|
|
_fontFamily = _he.FontFamily.Name;
|
|
_fontSize = (float)_he.FontSize;
|
|
_bytesPerRow = _he.BytesPerRow;
|
|
_rowHeight = _he.RowHeight;
|
|
_rowHeaderWidth = _he.RowHeaderWidth;
|
|
_columnHeaderHeight = _he.ColumnHeaderHeight;
|
|
_headerCharLength = _he.HeaderCharLength;
|
|
_headerForeground = _he.HeaderForeground.Color;
|
|
_highDensityMode = _he.HighDensityMode;
|
|
_dataProvider = _he.DataProvider;
|
|
_letterSize = _he.LetterSize;
|
|
_firstByte = _he.TopRow * _bytesPerRow;
|
|
|
|
_skiaEdging = fontAntialiasing switch {
|
|
FontAntialiasing.Disabled => SKFontEdging.Alias,
|
|
FontAntialiasing.Antialias => SKFontEdging.Antialias,
|
|
FontAntialiasing.SubPixelAntialias or _ => SKFontEdging.SubpixelAntialias
|
|
};
|
|
_skiaSubpixelSmoothing = fontAntialiasing == FontAntialiasing.SubPixelAntialias;
|
|
}
|
|
|
|
public Rect Bounds { get; private set; }
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
public bool Equals(ICustomDrawOperation? other) => false;
|
|
public bool HitTest(Point p) => false;
|
|
|
|
public void Render(ImmediateDrawingContext context)
|
|
{
|
|
var leaseFeature = context.PlatformImpl.GetFeature<ISkiaSharpApiLeaseFeature>();
|
|
using var lease = leaseFeature?.Lease();
|
|
var canvas = lease?.SkCanvas;
|
|
if(canvas == null) {
|
|
//context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
|
|
} else {
|
|
canvas.Save();
|
|
|
|
canvas.Translate(0, 0);
|
|
|
|
SKPaint paint = new SKPaint();
|
|
paint.Color = new SKColor(ColorHelper.GetColor(_headerForeground).ToUInt32());
|
|
|
|
SKTypeface typeface = SKTypeface.FromFamilyName(_fontFamily);
|
|
SKFont font = new SKFont(typeface, _fontSize);
|
|
font.Edging = _skiaEdging;
|
|
font.Subpixel = _skiaSubpixelSmoothing;
|
|
|
|
//Fixes layout issues (on Linux)
|
|
font.Hinting = SKFontHinting.Full;
|
|
font.LinearMetrics = true;
|
|
|
|
using var builder = new SKTextBlobBuilder();
|
|
|
|
int dataLength = _dataProvider.Length;
|
|
int bytesPerRow = _bytesPerRow;
|
|
|
|
double rowHeaderWidth = _rowHeaderWidth;
|
|
double textWidth = _headerCharLength * _letterSize.Width;
|
|
double xOffset = (rowHeaderWidth - textWidth) / 2;
|
|
|
|
int headerByte = _firstByte;
|
|
double y = 0;
|
|
int row = 0;
|
|
double drawOffsetY = _highDensityMode ? -_rowHeight * 0.1 : -_rowHeight * 0.2;
|
|
|
|
//Draw row headers for each row
|
|
while(y < Bounds.Height && headerByte < dataLength) {
|
|
string rowText = headerByte.ToString("X" + _headerCharLength);
|
|
int count = font.CountGlyphs(rowText);
|
|
var buffer = builder.AllocateRun(font, count, (float)xOffset, (float)(row * _rowHeight + drawOffsetY));
|
|
font.GetGlyphs(rowText, buffer.GetGlyphSpan());
|
|
row++;
|
|
y += _rowHeight;
|
|
headerByte += bytesPerRow;
|
|
}
|
|
|
|
SKTextBlob? textToDraw = builder.Build();
|
|
if(textToDraw != null) {
|
|
canvas.DrawText(textToDraw, 0, (float)_rowHeight, paint);
|
|
}
|
|
|
|
canvas.Restore();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|