Sprite Viewer: Refactoring, fixes for tooltips, added some options

This commit is contained in:
Sour 2021-12-31 14:48:23 -05:00
parent e6a79f0133
commit 7de29dcc35
11 changed files with 230 additions and 102 deletions

View file

@ -112,6 +112,8 @@ struct DebugSpritePreviewInfo
uint32_t Width;
uint32_t Height;
uint32_t SpriteCount;
int32_t CoordOffsetX;
int32_t CoordOffsetY;
};
class PpuTools

View file

@ -151,6 +151,8 @@ DebugSpritePreviewInfo GbPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions
info.Height = 256;
info.Width = 256;
info.SpriteCount = 40;
info.CoordOffsetX = 0;
info.CoordOffsetY = 0;
return info;
}

View file

@ -144,9 +144,10 @@ void NesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& b
DebugSpriteInfo sprite;
for(int i = 0x100 - 4; i >= 0; i -= 4) {
GetSpriteInfo(sprite, i / 4, options, state, vram, oamRam, palette);
int spritePosY = sprite.Y + 1;
for(int y = 0; y < sprite.Height; y++) {
if(sprite.Y + y >= 256) {
if(spritePosY + y >= 256) {
break;
}
@ -157,7 +158,7 @@ void NesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& b
uint32_t color = sprite.SpritePreview[y * sprite.Width + x];
if(color != 0) {
outBuffer[((sprite.Y + y) * 256) + sprite.X + x] = color;
outBuffer[((spritePosY + y) * 256) + sprite.X + x] = color;
}
}
}
@ -221,7 +222,7 @@ DebugTilemapTileInfo NesPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uin
void NesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t i, GetSpritePreviewOptions& options, NesPpuState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette)
{
sprite.SpriteIndex = i;
sprite.Y = oamRam[i * 4] + 1;
sprite.Y = oamRam[i * 4];
sprite.X = oamRam[i * 4 + 3];
sprite.TileIndex = oamRam[i * 4 + 1];
sprite.TileAddress = ((state.ControlReg & 0x08) ? 0x1000 : 0x0000) | (sprite.TileIndex * 16);
@ -232,7 +233,7 @@ void NesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t i, GetSpritePr
sprite.HorizontalMirror = (attributes & 0x40) != 0;
sprite.VerticalMirror = (attributes & 0x80) != 0;
sprite.Priority = (attributes & 0x20) ? DebugSpritePriority::Background : DebugSpritePriority::Foreground;
sprite.Visible = sprite.Y < 240;
sprite.Visible = sprite.Y < 239;
sprite.Width = 8;
bool largeSprites = (state.ControlReg & 0x20) ? true : false;
@ -286,5 +287,7 @@ DebugSpritePreviewInfo NesPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions
info.Height = 256;
info.Width = 256;
info.SpriteCount = 64;
info.CoordOffsetX = 0;
info.CoordOffsetY = 1;
return info;
}

View file

@ -366,5 +366,7 @@ DebugSpritePreviewInfo SnesPpuTools::GetSpritePreviewInfo(GetSpritePreviewOption
info.Height = 256;
info.Width = 512;
info.SpriteCount = 128;
info.CoordOffsetX = 256;
info.CoordOffsetY = 0;
return info;
}

View file

@ -5,9 +5,13 @@ namespace Mesen.Config
public class SpriteViewerConfig : BaseWindowConfig<SpriteViewerConfig>
{
[Reactive] public bool ShowSettingsPanel { get; set; } = true;
[Reactive] public bool ShowCoordsInHex { get; set; } = false;
[Reactive] public bool ShowOutline { get; set; } = false;
[Reactive] public bool HideOffscreenSprites { get; set; } = false;
[Reactive] public int ImageScale { get; set; } = 2;
[Reactive] public RefreshTimingConfig RefreshTiming { get; set; } = new();
[Reactive] public bool HideOffscreenSprites { get; set; } = false;
public SpriteViewerConfig()
{

View file

@ -8,6 +8,7 @@ using Avalonia.Media;
using Avalonia.Threading;
using Mesen.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Mesen.Debugger.Controls
@ -26,10 +27,13 @@ namespace Mesen.Debugger.Controls
public static readonly StyledProperty<bool> AllowSelectionProperty = AvaloniaProperty.Register<PictureViewer, bool>(nameof(AllowSelection), true);
public static readonly StyledProperty<bool> ShowMousePositionProperty = AvaloniaProperty.Register<PictureViewer, bool>(nameof(ShowMousePosition), true);
public static readonly StyledProperty<Rect?> MouseOverRectProperty = AvaloniaProperty.Register<PictureViewer, Rect?>(nameof(MouseOverRect), null, defaultBindingMode: BindingMode.OneWay);
public static readonly StyledProperty<Rect> SelectionRectProperty = AvaloniaProperty.Register<PictureViewer, Rect>(nameof(SelectionRect), Rect.Empty, defaultBindingMode: BindingMode.TwoWay);
public static readonly StyledProperty<Rect> OverlayRectProperty = AvaloniaProperty.Register<PictureViewer, Rect>(nameof(OverlayRect), Rect.Empty);
public static readonly StyledProperty<List<Rect>?> HighlightRectsProperty = AvaloniaProperty.Register<PictureViewer, List<Rect>?>(nameof(HighlightRects), null);
public static readonly RoutedEvent<PositionClickedEventArgs> PositionClickedEvent = RoutedEvent.Register<PictureViewer, PositionClickedEventArgs>(nameof(PositionClicked), RoutingStrategies.Bubble);
public event EventHandler<PositionClickedEventArgs> PositionClicked
{
@ -114,11 +118,25 @@ namespace Mesen.Debugger.Controls
set { SetValue(OverlayRectProperty, value); }
}
private PixelPoint? _mousePosition = null;
public Rect? MouseOverRect
{
get { return GetValue(MouseOverRectProperty); }
set { SetValue(MouseOverRectProperty, value); }
}
public List<Rect>? HighlightRects
{
get { return GetValue(HighlightRectsProperty); }
set { SetValue(HighlightRectsProperty, value); }
}
static PictureViewer()
{
AffectsRender<PictureViewer>(SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty, ShowGridProperty, SelectionRectProperty, OverlayRectProperty);
AffectsRender<PictureViewer>(
SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty,
ShowGridProperty, SelectionRectProperty, OverlayRectProperty,
HighlightRectsProperty, MouseOverRectProperty
);
SourceProperty.Changed.AddClassHandler<PictureViewer>((x, e) => {
x.UpdateSize();
@ -202,16 +220,20 @@ namespace Mesen.Debugger.Controls
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
PixelPoint? p = GetGridPointFromMousePoint(e.GetCurrentPoint(this).Position);
_mousePosition = p;
InvalidateVisual();
if(ShowMousePosition) {
PixelPoint? p = GetGridPointFromMousePoint(e.GetCurrentPoint(this).Position);
if(p != null) {
MouseOverRect = GetTileRect(p.Value);
} else {
MouseOverRect = null;
}
}
}
protected override void OnPointerLeave(PointerEventArgs e)
{
base.OnPointerLeave(e);
_mousePosition = null;
InvalidateVisual();
MouseOverRect = null;
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
@ -306,21 +328,6 @@ namespace Mesen.Debugger.Controls
DrawGrid(context, ShowGrid, GridSizeX, GridSizeY, Color.FromArgb(128, Colors.LightBlue.R, Colors.LightBlue.G, Colors.LightBlue.B));
DrawGrid(context, ShowAltGrid, AltGridSizeX, AltGridSizeY, Color.FromArgb(128, Colors.Red.R, Colors.Red.G, Colors.Red.B));
if(ShowMousePosition && _mousePosition.HasValue) {
PixelPoint p = _mousePosition.Value;
Rect rect = ToDrawRect(GetTileRect(p));
context.DrawRectangle(new Pen(0x40000000, 2), rect.Inflate(0.5));
context.DrawRectangle(new Pen(Brushes.White, 2), rect.Inflate(0.5));
}
if(SelectionRect != Rect.Empty) {
Rect rect = ToDrawRect(SelectionRect);
DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, (double)(_stopWatch.ElapsedMilliseconds / 50) % 100 / 5);
context.DrawRectangle(new Pen(0x40000000, 2), rect.Inflate(0.5));
context.DrawRectangle(new Pen(Brushes.White, 2, dashes), rect.Inflate(0.5));
}
if(OverlayRect != Rect.Empty) {
Rect rect = ToDrawRect(OverlayRect);
Brush brush = new SolidColorBrush(Colors.Gray, 0.4);
@ -347,6 +354,29 @@ namespace Mesen.Debugger.Controls
}
}
}
if(HighlightRects?.Count > 0) {
Pen pen = new Pen(Brushes.LightSteelBlue, 1);
foreach(Rect highlightRect in HighlightRects) {
Rect rect = ToDrawRect(highlightRect);
context.DrawRectangle(pen, rect);
}
}
if(MouseOverRect != null && MouseOverRect != Rect.Empty) {
Rect rect = ToDrawRect(MouseOverRect.Value);
DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, 0);
context.DrawRectangle(new Pen(Brushes.DimGray, 2), rect.Inflate(0.5));
context.DrawRectangle(new Pen(Brushes.LightYellow, 2, dashes), rect.Inflate(0.5));
}
if(SelectionRect != Rect.Empty) {
Rect rect = ToDrawRect(SelectionRect);
DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, _stopWatch.ElapsedMilliseconds / 250.0);
context.DrawRectangle(new Pen(Brushes.Black, 2), rect.Inflate(0.5));
context.DrawRectangle(new Pen(Brushes.White, 2, dashes), rect.Inflate(0.5));
}
}
}

View file

@ -16,11 +16,12 @@ using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace Mesen.Debugger.ViewModels
{
public class SpriteViewerViewModel : ViewModelBase
public class SpriteViewerViewModel : ViewModelBase, IDisposable
{
public SpriteViewerConfig Config { get; }
public RefreshTimingViewModel RefreshTiming { get; }
@ -28,15 +29,29 @@ namespace Mesen.Debugger.ViewModels
public ConsoleType ConsoleType { get; }
[Reactive] public SpritePreviewModel? SelectedSprite { get; set; }
[Reactive] public DynamicTooltip? PreviewPanel { get; set; }
[Reactive] public DynamicTooltip? SelectedPreviewPanel { get; set; }
[Reactive] public DynamicTooltip? PreviewPanelTooltip { get; set; }
[Reactive] public SpritePreviewModel? PreviewPanelSprite { get; set; }
[Reactive] public DynamicTooltip? ViewerTooltip { get; set; }
[Reactive] public PixelPoint? ViewerMousePos { get; set; }
[Reactive] public DynamicBitmap ViewerBitmap { get; private set; }
[Reactive] public Rect SelectionRect { get; set; }
[Reactive] public Rect? MouseOverRect { get; set; }
[Reactive] public List<SpritePreviewModel> SpritePreviews { get; set; } = new();
[Reactive] public List<Rect>? SpriteRects { get; set; } = null;
public List<object> FileMenuActions { get; } = new();
public List<object> ViewMenuActions { get; } = new();
private Grid _spriteGrid;
private BaseState? _ppuState;
private byte[] _spriteRam = Array.Empty<byte>();
private byte[] _vram = Array.Empty<byte>();
private UInt32[] _palette = Array.Empty<UInt32>();
[Obsolete("For designer only")]
public SpriteViewerViewModel() : this(CpuType.Cpu, ConsoleType.Snes, new PictureViewer(), new Grid(), null) { }
@ -93,17 +108,34 @@ namespace Mesen.Debugger.ViewModels
}
this.WhenAnyValue(x => x.SelectedSprite).Subscribe(x => UpdateSelectionPreview());
this.WhenAnyValue(x => x.ViewerMousePos, x => x.PreviewPanelSprite).Subscribe(x => UpdateMouseOverRect());
ReactiveHelper.RegisterRecursiveObserver(Config, Config_PropertyChanged);
DebugShortcutManager.RegisterActions(wnd, FileMenuActions);
DebugShortcutManager.RegisterActions(wnd, ViewMenuActions);
}
public DynamicTooltip? GetPreviewPanel(SpritePreviewModel sprite, DynamicTooltip? existingTooltip)
public void Dispose()
{
ReactiveHelper.UnregisterRecursiveObserver(Config, Config_PropertyChanged);
}
private void Config_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
RefreshTab();
}
public DynamicTooltip GetPreviewPanel(SpritePreviewModel sprite, DynamicTooltip? existingTooltip)
{
TooltipEntries entries = existingTooltip?.Items ?? new();
entries.AddPicture("Sprite", sprite.SpritePreview, (int)sprite.SpritePreviewZoom);
entries.AddEntry("Sprite index", sprite.SpriteIndex.ToString());
entries.AddEntry("X, Y", sprite.X + ", " + sprite.Y);
if(Config.ShowCoordsInHex) {
entries.AddEntry("X, Y", "$" + sprite.X.ToString("X2") + ", $" + sprite.Y.ToString("X2"));
} else {
entries.AddEntry("X, Y", sprite.X + ", " + sprite.Y);
}
entries.AddEntry("Size", sprite.Width + "x" + sprite.Height);
entries.AddEntry("Tile index", "$" + sprite.TileIndex.ToString("X2"));
@ -125,15 +157,6 @@ namespace Mesen.Debugger.ViewModels
}
}
public void UpdateSelectionPreview()
{
if(SelectedSprite != null) {
PreviewPanel = GetPreviewPanel(SelectedSprite, PreviewPanel);
} else {
PreviewPanel = null;
}
}
[MemberNotNull(nameof(ViewerBitmap))]
private void InitBitmap(int width, int height)
{
@ -178,21 +201,19 @@ namespace Mesen.Debugger.ViewModels
ToolTip.SetTip(ctrl, null);
ToolTip.SetIsOpen(ctrl, false);
}
PreviewPanelTooltip = null;
PreviewPanelSprite = null;
}
private void SpritePreview_PointerEnter(object? sender, PointerEventArgs e)
{
if(sender is Control ctrl && ctrl.DataContext is SpritePreviewModel sprite) {
DynamicTooltip? tooltip = GetPreviewPanel(sprite, null);
PreviewPanelTooltip = GetPreviewPanel(sprite, PreviewPanelTooltip);
PreviewPanelSprite = sprite;
if(tooltip != null) {
ToolTip.SetTip(ctrl, tooltip);
ToolTip.SetHorizontalOffset(ctrl, 15);
ToolTip.SetIsOpen(ctrl, true);
} else {
ToolTip.SetTip(ctrl, null);
ToolTip.SetIsOpen(ctrl, false);
}
ToolTip.SetTip(ctrl, PreviewPanelTooltip);
ToolTip.SetHorizontalOffset(ctrl, 15);
ToolTip.SetIsOpen(ctrl, true);
}
}
@ -204,19 +225,19 @@ namespace Mesen.Debugger.ViewModels
}
}
private void InitPreviews(DebugSpriteInfo[] sprites)
private void InitPreviews(DebugSpriteInfo[] sprites, DebugSpritePreviewInfo previewInfo)
{
if(SpritePreviews.Count != sprites.Length) {
List<SpritePreviewModel> previews = new();
for(int i = 0; i < sprites.Length; i++) {
SpritePreviewModel model = new SpritePreviewModel();
model.Init(ref sprites[i]);
model.Init(ref sprites[i], previewInfo);
previews.Add(model);
}
SpritePreviews = previews;
} else {
for(int i = 0; i < sprites.Length; i++) {
SpritePreviews[i].Init(ref sprites[i]);
SpritePreviews[i].Init(ref sprites[i], previewInfo);
}
}
@ -234,26 +255,45 @@ namespace Mesen.Debugger.ViewModels
private void RefreshData<T>() where T : struct, BaseState
{
T ppuState = DebugApi.GetPpuState<T>(CpuType);
byte[] vram = DebugApi.GetMemoryState(CpuType.GetVramMemoryType());
byte[] spriteRam = DebugApi.GetMemoryState(CpuType.GetSpriteRamMemoryType());
UInt32[] palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType);
_ppuState = DebugApi.GetPpuState<T>(CpuType);
_vram = DebugApi.GetMemoryState(CpuType.GetVramMemoryType());
_spriteRam = DebugApi.GetMemoryState(CpuType.GetSpriteRamMemoryType());
_palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType);
RefreshTab();
}
private void RefreshTab()
{
Dispatcher.UIThread.Post(() => {
if(_ppuState == null) {
return;
}
GetSpritePreviewOptions options = new GetSpritePreviewOptions() {
SelectedSprite = -1
};
UInt32[] palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType);
DebugSpritePreviewInfo size = DebugApi.GetSpritePreviewInfo(CpuType, options, ppuState);
InitBitmap((int)size.Width, (int)size.Height);
DebugSpritePreviewInfo previewInfo = DebugApi.GetSpritePreviewInfo(CpuType, options, _ppuState);
InitBitmap((int)previewInfo.Width, (int)previewInfo.Height);
using(var framebuffer = ViewerBitmap.Lock()) {
DebugApi.GetSpritePreview(CpuType, options, ppuState, vram, spriteRam, palette, framebuffer.FrameBuffer.Address);
DebugApi.GetSpritePreview(CpuType, options, _ppuState, _vram, _spriteRam, palette, framebuffer.FrameBuffer.Address);
}
DebugSpriteInfo[] sprites = DebugApi.GetSpriteList(CpuType, options, ppuState, vram, spriteRam, palette);
InitPreviews(sprites);
DebugSpriteInfo[] sprites = DebugApi.GetSpriteList(CpuType, options, _ppuState, _vram, _spriteRam, palette);
InitPreviews(sprites, previewInfo);
if(Config.ShowOutline) {
List<Rect> spriteRects = new List<Rect>();
foreach(SpritePreviewModel sprite in SpritePreviews) {
spriteRects.Add(sprite.GetPreviewRect());
}
SpriteRects = spriteRects;
} else {
SpriteRects = null;
}
int selectedIndex = SelectedSprite?.SpriteIndex ?? -1;
if(selectedIndex >= 0 && selectedIndex < SpritePreviews.Count) {
@ -263,18 +303,49 @@ namespace Mesen.Debugger.ViewModels
SelectedSprite = null;
}
UpdateTooltips();
UpdateSelection(SelectedSprite);
UpdateMouseOverRect();
});
}
private void UpdateSelectionPreview()
{
if(SelectedSprite != null) {
SelectedPreviewPanel = GetPreviewPanel(SelectedSprite, SelectedPreviewPanel);
} else {
SelectedPreviewPanel = null;
}
}
private void UpdateMouseOverRect()
{
if(PreviewPanelSprite != null) {
MouseOverRect = PreviewPanelSprite.GetPreviewRect();
} else if(ViewerMousePos != null && GetMatchingSprite(ViewerMousePos.Value) is SpritePreviewModel sprite) {
MouseOverRect = sprite.GetPreviewRect();
} else {
MouseOverRect = null;
}
}
private void UpdateTooltips()
{
if(PreviewPanelSprite != null && PreviewPanelTooltip != null) {
GetPreviewPanel(PreviewPanelSprite, PreviewPanelTooltip);
} else if(ViewerMousePos != null && ViewerTooltip != null) {
SpritePreviewModel? sprite = GetMatchingSprite(ViewerMousePos.Value);
if(sprite != null) {
GetPreviewPanel(sprite, ViewerTooltip);
}
}
}
public void UpdateSelection(SpritePreviewModel? sprite)
{
if(sprite != null) {
int offset = 0;
if(CpuType == CpuType.Cpu) {
offset = 256;
}
SelectionRect = new Rect(sprite.X + offset, sprite.Y, sprite.Width, sprite.Height);
SelectionRect = sprite.GetPreviewRect();
} else {
SelectionRect = Rect.Empty;
}
@ -282,11 +353,10 @@ namespace Mesen.Debugger.ViewModels
public SpritePreviewModel? GetMatchingSprite(PixelPoint p)
{
foreach(SpritePreviewModel sprite in SpritePreviews) {
if(
p.X >= sprite.X && p.X < sprite.X + sprite.Width &&
p.Y >= sprite.Y && p.Y < sprite.Y + sprite.Height
) {
Point point = p.ToPoint(1);
for(int i = SpritePreviews.Count - 1; i >= 0; i--) {
SpritePreviewModel sprite = SpritePreviews[i];
if(sprite.GetPreviewRect().Contains(point)) {
return sprite;
}
}
@ -300,6 +370,8 @@ namespace Mesen.Debugger.ViewModels
[Reactive] public int SpriteIndex { get; set; }
[Reactive] public int X { get; set; }
[Reactive] public int Y { get; set; }
[Reactive] public int PreviewX { get; set; }
[Reactive] public int PreviewY { get; set; }
[Reactive] public int Width { get; set; }
[Reactive] public int Height { get; set; }
[Reactive] public int TileIndex { get; set; }
@ -317,11 +389,13 @@ namespace Mesen.Debugger.ViewModels
[Reactive] public DynamicBitmap SpritePreview { get; set; } = new DynamicBitmap(new PixelSize(1, 1), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul);
[Reactive] public double SpritePreviewZoom { get; set; }
public unsafe void Init(ref DebugSpriteInfo sprite)
public unsafe void Init(ref DebugSpriteInfo sprite, DebugSpritePreviewInfo previewInfo)
{
SpriteIndex = sprite.SpriteIndex;
X = sprite.X;
Y = sprite.Y;
PreviewX = sprite.X + previewInfo.CoordOffsetX;
PreviewY = sprite.Y + previewInfo.CoordOffsetY;
Width = sprite.Width;
Height = sprite.Height;
TileIndex = sprite.TileIndex;
@ -351,5 +425,10 @@ namespace Mesen.Debugger.ViewModels
HorizontalMirror = sprite.HorizontalMirror;
VerticalMirror = sprite.VerticalMirror;
}
public Rect GetPreviewRect()
{
return new Rect(PreviewX, PreviewY, Width, Height);
}
}
}

View file

@ -50,7 +50,8 @@
<Panel DockPanel.Dock="Right" IsVisible="{CompiledBinding Config.ShowSettingsPanel}">
<ScrollViewer>
<StackPanel Margin="5">
<CheckBox Content="{l:Translate chkHideOffscreenSprites}" IsChecked="{CompiledBinding Config.HideOffscreenSprites}" />
<CheckBox Content="{l:Translate chkShowCoordsInHex}" IsChecked="{CompiledBinding Config.ShowCoordsInHex}" />
<CheckBox Content="{l:Translate chkShowOutline}" IsChecked="{CompiledBinding Config.ShowOutline}" />
<dv:RefreshTimingView DataContext="{CompiledBinding RefreshTiming}" />
@ -60,9 +61,9 @@
Margin="0 10"
Padding="3"
MinWidth="170"
IsVisible="{CompiledBinding PreviewPanel, Converter={x:Static ObjectConverters.IsNotNull}}"
IsVisible="{CompiledBinding SelectedPreviewPanel, Converter={x:Static ObjectConverters.IsNotNull}}"
>
<ContentControl Content="{CompiledBinding PreviewPanel}" />
<ContentControl Content="{CompiledBinding SelectedPreviewPanel}" />
</Border>
</StackPanel>
</ScrollViewer>
@ -82,6 +83,8 @@
Source="{CompiledBinding ViewerBitmap}"
Zoom="{CompiledBinding Config.ImageScale}"
SelectionRect="{CompiledBinding SelectionRect}"
MouseOverRect="{CompiledBinding MouseOverRect}"
HighlightRects="{CompiledBinding SpriteRects}"
ShowMousePosition="False"
/>
</ScrollViewer>

View file

@ -1,5 +1,3 @@
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
@ -8,10 +6,8 @@ using Mesen.Debugger.Controls;
using Mesen.Debugger.ViewModels;
using Mesen.Interop;
using System.ComponentModel;
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Mesen.Debugger.Utilities;
namespace Mesen.Debugger.Windows
@ -20,7 +16,6 @@ namespace Mesen.Debugger.Windows
{
private NotificationListener _listener;
private SpriteViewerViewModel _model;
private PixelPoint? _prevMousePos = null;
[Obsolete("For designer only")]
public SpriteViewerWindow() : this(CpuType.Cpu, ConsoleType.Snes) { }
@ -67,31 +62,28 @@ namespace Mesen.Debugger.Windows
protected override void OnClosing(CancelEventArgs e)
{
_listener?.Dispose();
_listener.Dispose();
_model.Config.SaveWindowSettings(this);
_model.Dispose();
}
private void PicViewer_PointerMoved(object? sender, PointerEventArgs e)
{
if(sender is PictureViewer viewer) {
PixelPoint? point = viewer.GetGridPointFromMousePoint(e.GetCurrentPoint(viewer).Position);
if(point == _prevMousePos) {
if(point == _model.ViewerMousePos) {
return;
}
_prevMousePos = point;
_model.ViewerMousePos = point;
DynamicTooltip? tooltip = null;
if(point != null) {
DynamicTooltip? existingTooltip = ToolTip.GetTip(viewer) as DynamicTooltip;
if(_model.CpuType == CpuType.Cpu) {
point = point.Value.WithX(point.Value.X - 256);
}
SpritePreviewModel? sprite = _model.GetMatchingSprite(point.Value);
tooltip = sprite == null ? null : _model.GetPreviewPanel(sprite, existingTooltip);
tooltip = sprite == null ? null : _model.GetPreviewPanel(sprite, _model.ViewerTooltip);
}
if(tooltip != null) {
_model.ViewerTooltip = tooltip;
ToolTip.SetTip(viewer, tooltip);
//Force tooltip to update its position
@ -101,6 +93,7 @@ namespace Mesen.Debugger.Windows
} else {
ToolTip.SetTip(viewer, null);
ToolTip.SetIsOpen(viewer, false);
_model.ViewerTooltip = null;
}
}
}
@ -111,17 +104,14 @@ namespace Mesen.Debugger.Windows
ToolTip.SetTip(viewer, null);
ToolTip.SetIsOpen(viewer, false);
}
_prevMousePos = null;
_model.ViewerTooltip = null;
_model.ViewerMousePos = null;
}
private void PicViewer_PositionClicked(object? sender, PositionClickedEventArgs e)
{
if(sender is PictureViewer viewer) {
PixelPoint p = e.Position;
if(_model.CpuType == CpuType.Cpu) {
p = p.WithX(p.X - 256);
}
SpritePreviewModel? sprite = _model.GetMatchingSprite(p);
_model.SelectedSprite = sprite;
_model.UpdateSelection(sprite);

View file

@ -178,25 +178,34 @@ namespace Mesen.Interop
[DllImport(DllPath)] public static extern void GetTileView(CpuType cpuType, GetTileViewOptions options, byte[] source, int srcSize, UInt32[] palette, IntPtr buffer);
[DllImport(DllPath)] private static extern void GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr buffer);
public unsafe static void GetSpritePreview<T>(CpuType cpuType, GetSpritePreviewOptions options, T state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr outputBuffer) where T : struct, BaseState
public unsafe static void GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, BaseState state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr outputBuffer)
{
byte* ptr = stackalloc byte[Marshal.SizeOf(typeof(T))];
Debug.Assert(state.GetType().IsValueType);
Debug.Assert(IsValidPpuState(ref state, cpuType));
byte* ptr = stackalloc byte[Marshal.SizeOf(state.GetType())];
Marshal.StructureToPtr(state, (IntPtr)ptr, false);
DebugApi.GetSpritePreview(cpuType, options, (IntPtr)ptr, vram, spriteRam, palette, outputBuffer);
}
[DllImport(DllPath)] private static extern DebugSpritePreviewInfo GetSpritePreviewInfo(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state);
public unsafe static DebugSpritePreviewInfo GetSpritePreviewInfo<T>(CpuType cpuType, GetSpritePreviewOptions options, T state) where T : struct, BaseState
public unsafe static DebugSpritePreviewInfo GetSpritePreviewInfo(CpuType cpuType, GetSpritePreviewOptions options, BaseState state)
{
byte* ptr = stackalloc byte[Marshal.SizeOf(typeof(T))];
Debug.Assert(state.GetType().IsValueType);
Debug.Assert(IsValidPpuState(ref state, cpuType));
byte* ptr = stackalloc byte[Marshal.SizeOf(state.GetType())];
Marshal.StructureToPtr(state, (IntPtr)ptr, false);
return DebugApi.GetSpritePreviewInfo(cpuType, options, (IntPtr)ptr);
}
[DllImport(DllPath)] private static extern void GetSpriteList(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr sprites);
public unsafe static DebugSpriteInfo[] GetSpriteList<T>(CpuType cpuType, GetSpritePreviewOptions options, T state, byte[] vram, byte[] spriteRam, UInt32[] palette) where T : struct, BaseState
public unsafe static DebugSpriteInfo[] GetSpriteList(CpuType cpuType, GetSpritePreviewOptions options, BaseState state, byte[] vram, byte[] spriteRam, UInt32[] palette)
{
byte* statePtr = stackalloc byte[Marshal.SizeOf(typeof(T))];
Debug.Assert(state.GetType().IsValueType);
Debug.Assert(IsValidPpuState(ref state, cpuType));
byte* statePtr = stackalloc byte[Marshal.SizeOf(state.GetType())];
Marshal.StructureToPtr(state, (IntPtr)statePtr, false);
DebugSpriteInfo[] sprites = new DebugSpriteInfo[GetSpritePreviewInfo(cpuType, options, (IntPtr)statePtr).SpriteCount];
@ -848,6 +857,8 @@ namespace Mesen.Interop
public UInt32 Width;
public UInt32 Height;
public UInt32 SpriteCount;
public Int32 CoordOffsetX;
public Int32 CoordOffsetY;
}
public enum DebugSpritePriority

View file

@ -808,8 +808,10 @@
<Control ID="colPriority">Priority</Control>
<Control ID="colPalette">Palette</Control>
<Control ID="colFlags">Flags</Control>
<Control ID="chkHideOffscreenSprites">Hide off-screen sprites</Control>
<Control ID="chkShowOutline">Show outline around sprites</Control>
<Control ID="chkShowCoordsInHex">Show coordinates in hex</Control>
</Form>
<Form ID="AssemblerWindow">